Mobx observer is not reacting to changes in observable - react-native

After action call, the observable gets updated with new values but it doesn't trigger update in my observer class. The reaction occurs when I put an condition check in render which uses observable object. But I use observable object in another place inside the returned DOM as a prop condition. I couldn't understand why this happens.
Here is my observer class
#inject("store")
#observer
export default class SignupWithMobileNo extends Component {
constructor() {
super();
this.sendOTP = this.sendOTP.bind(this);
this.state = {
phoneInput: ""
};
}
static navigationOptions = {
header: null
};
componentDidMount() {
BackHandler.addEventListener("hardwareBackPress", this.handleBackButton);
}
handleBackButton() {
ToastAndroid.show("You cannot go back", ToastAndroid.SHORT);
return true;
}
sendOTP(phone) {
this.props.store.userStore.sendOTP(phone);
}
componentDidUpdate() {
console.log("component did update", this.props);
const navigation = this.props.navigation;
const { sendOTPRequest } = this.props.store.userStore;
if (sendOTPRequest.state === "succeeded") {
navigation.navigate("VerifyOTP");
}
}
render() {
const navigation = this.props.navigation;
const { sendOTPRequest } = this.props.store.userStore;
// reaction occurs when I uncomment the following lines.
// if (sendOTPRequest.state === "succeeded") {
// }
return(
<View style={styles.container}>
<Formik
initialValues={{
phone: ""
}}
onSubmit={values => {
this.sendOTP(values.phone);
}}
validate={values => {
let errors = {};
if (values.phone.length < 1) {
errors.phone = "Invalid phone number";
}
return errors;
}}
>
{({
handleChange,
handleSubmit,
setFieldTouched,
values,
errors,
touched
}) => (
<View style={styles.formBody}>
<Text style={styles.headline}>Get authenticate your account</Text>
<FormInput
onChange={handleChange("phone")}
value={values.phone}
placeholder="Enter your phone number"
keyboardType="phone-pad"
onBlur={() => {
setFieldTouched("phone");
}}
/>
<FormButton
onClickHandler={handleSubmit}
buttonText="Send OTP"
isDisabled={
values.phone.length < 1 ||
sendOTPRequest.state === "requested"
}
/>
{touched.phone && errors.phone ? (
<Text style={styles.body}> {errors.phone} </Text>
) : null}
{sendOTPRequest.state === "failed" ? (
<Text style={styles.body}> {sendOTPRequest.error_code</Text>
) : null}
</View>
)}
</Formik>
</View>
);
}
}

No subscribers to observable data in the observer's render function. Once I added that, the issue solved.

Related

React Native Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state

Im learning react native, and i try to use state, now im facing an issue "Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state."
Here my code
class Quantity extends React.Component {
constructor(props) {
super(props);
this.state = {
qty:1
};
this.setQty = this.setQty.bind(this);
}
setQty = (e) =>{
this.setState({
qty:e,
});
}
componentDidMount() {
this.props.onRef(this)
this.state.qty = 1
}
componentWillUnmount() {
this.props.onRef(undefined)
}
getCheckoutQty() {
return this.state.qty.toString();
}
minusQty = () => {
let newQty = this.state.qty -1;
this.setQty(newQty)
}
plusQty = () => {
let newQty = this.state.qty +1;
this.setQty(newQty);
}
render() {
const {qty}=this.state
return (
<View style={styles.row}>
<TouchableOpacity style={styles.icon}
disabled={(this.state.qty==1)?true:false}
// onPress={() => this.minusQty()}
>
<Icon name="minus" color="#000" style={(this.state.qty==1)?{opacity:0.2}:{opacity:1}}/>
</TouchableOpacity>
<Input
style={styles.qtyBox}
keyboardType="numeric"
returnKeyType="done"
value={qty.toString()}
onChangeText={(e)=>this.setQty(this)}
/>
<TouchableOpacity style={styles.icon}
// onPress={() => this.plusQty()}
>
<Icon name="plus" color="#000" />
</TouchableOpacity>
</View>
);
}
}
any way to fix it?
Thank for the support

Appium React Native Not Ready for Text Input

I've recently switched over to Appium + webdriverIO for E2E testing. Everything is working pretty well except for one test case relating to text input.
Basically, the component under test is a login screen that uses redux-form for form management. I'm constantly getting the error "'"login-field" Other' is not ready for a text input. Neither the accessibility element itself nor its accessible descendants have the input focus". The components are as follow:
SignInScreen.tsx
export class SignInScreen extends React.Component<any> {
render() {
const { handleSubmit, submitting, style } = this.props;
return (
<View style={style}>
<View>
<View>
<Field
name="login"
component={Input}
accessibilityLabel="login-field"
testID="login-field"
/>
<Field
secureTextEntry
name="password"
component={Input}
accessibilityLabel="password-field"
testID="password-field"
/>
</View>
</View>
</View>
);
}
}
Input.tsx
export class Input extends React.Component {
render() {
const {
input,
meta: { error, active, focused },
accessibilityLabel,
testID
} = this.props;
const showError = !active && !!error && !focused;
const errorText = "ERROR!"
return (
<View style={[style, styles.container]}>
<TextInput
autoCapitalize="none"
value={input.value}
onChangeText={input.onChange}
onFocus={input.onFocus}
onBlur={input.onBlur}
accessibilityLabel={accessibilityLabel},
testID={testID}
/>
<View style={{height: 30}}>
{showError && (
<Text>{errorText}</Text>
)}
</View>
</View>
);
}
}
SignInScreen.test.ts
describe('Sign In Screen Test', () => {
let client;
beforeAll(async () => {
// set up code
});
afterAll(async () => {
// tear down code
});
it('Can login', async () => {
const loginField = await client.$('~login-field');
await loginField.setValue('test#gmail.com'); // error here
const passwordField = await client.$('~password-field');
await passwordField.set('password' + '\n');
});
});
I do realize that the test cases work when I either add an additional <TextInput /> on top of the existing <TextInput /> component in the Input.tsx component as follows:
Input.tsx
export class Input extends React.Component {
render() {
const {
input,
meta: { error, active, focused },
accessibilityLabel,
testID
} = this.props;
const showError = !active && !!error && !focused;
const errorText = "ERROR!"
return (
<View style={[style, styles.container]}>
<TextInput />
<TextInput
autoCapitalize="none"
value={input.value}
onChangeText={input.onChange}
onFocus={input.onFocus}
onBlur={input.onBlur}
accessibilityLabel={accessibilityLabel},
testID={testID}
/>
<View style={{height: 30}}>
{showError && (
<Text>{errorText}</Text>
)}
</View>
</View>
);
}
}
or I remove the fixed height in the View component that nests the error message as follows:
Input.tsx
export class Input extends React.Component {
render() {
const {
input,
meta: { error, active, focused },
accessibilityLabel,
testID
} = this.props;
const showError = !active && !!error && !focused;
const errorText = "ERROR!"
return (
<View style={[style, styles.container]}>
<TextInput
autoCapitalize="none"
value={input.value}
onChangeText={input.onChange}
onFocus={input.onFocus}
onBlur={input.onBlur}
accessibilityLabel={accessibilityLabel},
testID={testID}
/>
<View>
{showError && (
<Text>{errorText}</Text>
)}
</View>
</View>
);
}
}
So what gives? I'm really lost as to what's causing Appium to not pick up the input focus without making the above adjustments.
I believe this is a recent bug with Appium - https://github.com/appium/java-client/issues/1386
You should NOT specify accessibilityLabel for ios separate props for ios and android like so try next workaround:
export default function testID(id) {
return Platform.OS === 'android'
? {
accessible : true,
accessibilityLabel: id,
}
: {
testID: id,
};
}
and then
<TextInput
{...otherProps}
{...testID('some-testID')}
/>

Redux reducer not changing prop

I am making a todo list application with redux. I am able to add todos perfectly fine with redux however my toggle todos and remove todos are having problems.
The toggle todo action gets called by the redux store (I see it happening in the debugger), however, it does not update the prop to be the opposite of completed and I am not sure why.
I have tried playing around with the syntax and modeling other people's redux todo lists for hours but have not been able to solve this issue.
My toggleTodo and removeTodo actions:
export const toggleTodo = (item) => {
return {
type: TOGGLE_TODO,
id: item.id
};
};
export const removeTodo = (item) => {
return {
type: REMOVE_TODO,
id: item.id
};
};
My TodoReducer: // this is where I suspect the problem is
const initialState = {
todos: []
};
const todos = (state = initialState, action) => {
switch (action.type) {
case TOGGLE_TODO:
if (state.id !== action.id) {
return state;
}
return {
...state, completed: !state.todos.completed
};
case REMOVE_TODO: {
const newState = [...state];
newState.splice(action.id, 1);
return { ...newState };
}
My main flatlist where I call the actions:
render() {
return (
<View style={{ height: HEIGHT }}>
<FlatList
data={this.props.todos}
extraData={this.state}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => {
return (
<TodoItem
todoItem={item}
pressToToggle={() => this.props.toggleTodo(item)}
deleteTodo={() => this.props.removeTodo(item)}
/>
);
}}
/>
</View>
);
}
}
export default connect(mapStateToProps, { addTodo, toggleTodo, removeTodo })(MainTodo);
// I call the actions I am using here and don't use mapDispatchToProps
And my TodoItem component where I pass in the props:
class TodoItem extends Component {
render() {
const todoItem = this.props.todoItem;
return (
<View>
<TouchableOpacity
style={styles.todoItem}
onPress={this.props.pressToToggle}
>
<Text
style={{
color: todoItem.completed ? '#aaaaaa' : '#f5f5f5',
textDecorationLine: todoItem.completed ? 'line-through' : 'none',
fontSize: 16 }}
>
{todoItem.text}
</Text>
<Button
title='Remove'
color='#ff5330'
onPress={this.props.deleteTodo}
/>
</TouchableOpacity>
</View>
);
}
}
When I hit toggle todo instead of the prop changing and the line coming through over the text nothing happens.
And when I try to remove a todo I get this error- "invalid attempt to spread non-iterable instance."
when you pass a function to component, try to pass it's reference, instead of
<TodoItem
todoItem={item}
pressToToggle={() => this.props.toggleTodo(item)}
deleteTodo={() => this.props.removeTodo(item)}
/>
try
<TodoItem
todoItem={item}
pressToToggle={this.props.toggleTodo.bind(this)}
deleteTodo={this.props.removeTodo.bind(this)}
/>
and in your TodoItem component call the function like
class TodoItem extends Component {
render() {
const todoItem = this.props.todoItem;
return (
<View>
<TouchableOpacity
style={styles.todoItem}
onPress={() => this.props.pressToToggle(todoItem)} /* this line */
>
<Text
style={{
color: todoItem.completed ? '#aaaaaa' : '#f5f5f5',
textDecorationLine: todoItem.completed ? 'line-through' : 'none',
fontSize: 16 }}
>
{todoItem.text}
</Text>
<Button
title='Remove'
color='#ff5330'
onPress={this.props.deleteTodo}
/>
</TouchableOpacity>
</View>
);
}
}

How to Control async method react-native

I want to send data to another component. I get datas from
AsyncStorage.getItem('myKey');
But when start async, component start to render so it sends null data to another component.
here is my methods ;
componentWillMount(){
this.getdata();
}
getdata = async () => {
console.log("console1");
const value = await AsyncStorage.getItem('myKey');
console.log("console2");
let valuePrsed = JSON.parse(value);
if(valuePrsed.username != null && valuePrsed.password != null)
{
this.setState({values: valuePrsed});
}
}
and this is my render method ;
render() {
console.log("rende splashscreen: ", this.state.values);
let { fadeAnim } = this.state;
return (
<View style = {{flex:1}}>
<LoginForm profile = {this.state.values}/>
<Animated.View style={{ ...this.props.style, opacity: fadeAnim }} >
{this.props.children}
<ImageBackground style={styles.logo1} source={require('../../image/dataLogo.jpeg')} >
</ImageBackground>
</Animated.View>
</View>
);
}
I send datas to LoginForm. I want to ask one more question. If I use <LoginForm /> like this, it ruins my component. How can I send with different way ?
Only render if it's ready to render. the way I do it is initialize a state lets say isReady and set to false then set it to true when you have the value.
Would look like this:
export default class test extends Component {
constructor(props) {
super(props)
this.state = {
isReady:false
}
}
componentWillMount(){
this.getdata();
}
getdata = async () => {
const value = await AsyncStorage.getItem('myKey');
this.setState({isReady:true})
let valuePrsed = JSON.parse(value);
if(valuePrsed.username != null && valuePrsed.password != null)
{
this.setState({values: valuePrsed});
}
}
render() {
if(this.state.isReady){
return (
<View ref={ref => this.view = ref} onLayout={() => this.saveLayout()}>
</View>
)}else{
<View></View>
}
}
}
To your second question:
If you pass through LoginForm you can create a function there that gets the parameters and updates state, then pass that function to your other component and call the function with the values in paremeter. if you are using react navigation you can do it like so:
loginForm
updateValues(values){
this.setState({value:values})
}
To pass the function with react-navigation:
this.props.navigation.navigate('otherComponent',{updateValues:this.updateValues})
In your otherComponent you call the function like so:
otherComponent
this.props.navigation.state.params.updateValues(newValues);
How about checking for the values variable in the render method?
render() {
console.log("rende splashscreen: ", this.state.values);
let { fadeAnim } = this.state;
return (
this.state.values ?
<View style = {{flex:1}}>
<LoginForm profile = {this.state.values}/>
<Animated.View style={{ ...this.props.style, opacity: fadeAnim }} >
{this.props.children}
<ImageBackground style={styles.logo1} source={require('../../image/dataLogo.jpeg')} >
</ImageBackground>
</Animated.View>
</View>
: <></>
);
}
You can keep a default/initial values in state variable at first like this:
constructor(props){
this.state = {
values:{userName: '', password: ''}
}
}
And when the actual values are available you can set them in state and automatically re-rendering will occur.
Since AsyncStorage returns a promise you can use .then() syntax
componentDidMount(){
console.log("console1");
AsyncStorage.getItem('myKey').then(value=>{
let valuePrsed = JSON.parse(value);
if(valuePrsed.username != null && valuePrsed.password != null)
{
this.setState({values: valuePrsed});
}
}).catch(err=>{
console.log('err', err);
})
}

View inside curly braces not showing

I'm new in ReactNative. I'm following a tutorial from Udemy. I've been trying to show a value from a variable. its working in the instructor's video but not in my code. the code is given below:
export default class App extends React.Component {
state = {
placeName: '',
places: []
}
placeNameChangedHandler = val => {
this.setState({
placeName: val
})
}
placeSubmitHandler = () => {
if (this.state.placeName.trim() === "") {
return;
}
this.setState(prevState => {
return {
places: prevState.places.concat(prevState.placeName)
}
})
}
render() {
const placesOutput = this.state.places.map(place => {
<Text>{place}</Text>
})
return (
<View style={styles.container}>
<View style={styles.inputContainer}>
<TextInput
placeholder="Type something"
style={styles.placeInput}
value={this.state.placeName}
onChangeText={this.placeNameChangedHandler}/>
<Button
style={styles.placeButton}
onPress={this.placeSubmitHandler}
title="Add"/>
</View>
<View>
{this.placesOutput}
</View>
</View>
);
}
}
but the {placeOutput} is not showing anything. its working in the instructor's video but not in my code. What am I doing wrong?
You aren't returning anything in your map() function. Your render function should look like this:
render() {
const placesOutput = this.state.places.map(place => {
return <Text>{place}</Text> //Added return statement
})
return (
<View style={styles.container}>
<View style={styles.inputContainer}>
<TextInput
placeholder="Type something"
style={styles.placeInput}
value={this.state.placeName}
onChangeText={this.placeNameChangedHandler}/>
<Button
style={styles.placeButton}
onPress={this.placeSubmitHandler}
title="Add"/>
</View>
<View>
{this.placesOutput}
</View>
</View>
);
}
All I did was add a return statement in your this.state.places.map() function.