Home > other >  I can't pass arguments to a function inside props on React Native
I can't pass arguments to a function inside props on React Native

Time:05-30

I'm new to React Native development and I'm struggling to understand why does the argument that I pass on a function like <TouchableHighlight onPress={props.executeFunction(props.label)}> is a SyntheticBaseEvent, instead of the props.label itself

On App.js:37 console.log, it shows SyntheticBaseEvent {_reactName: 'onClick', _targetInst: null, type: 'click', nativeEvent: PointerEvent, target:

Am I losing reference to the original functions?

App.js:

import { StyleSheet, Text, View } from 'react-native';
import React, { Component } from 'react'
import Calculator from './Calculator';
import { LinearGradient } from 'expo-linear-gradient';

const initialState = {

  displayValue: '0',
  clearDisplay: false,
  previousOperation: null,
  operation: null,
  values: [null, null],
  currentPositionOnValues: 0,
  originalValue: 0

}

class App extends Component {
  
  constructor(props) {
    
    super(props);
    
    this.state = { ...initialState }
    this.clear = this.clear.bind(this);
    this.addDigit = this.addDigit.bind(this);

  }

  clear() {
    this.setState({ ...initialState });
  }

  addDigit(digit) {

    console.log(digit)

    if (digit === "." && this.state.displayValue.includes('.')) {

      // Prevent double decimals
      return

    }

    const clearDisplay = this.state.displayValue === '0' || this.state.clearDisplay

    /* 
        Boolean value saying if it's necessary to clear the display
        True if the currentValue display value is 0 or the variable this.state.clearDisplay is set to true
    */

    const currentValue = clearDisplay ? '' : this.state.displayValue

    /* 
        currentValue shows the 'cleared' value or the display value
    */

    const displayValue = currentValue   digit

    this.setState({ displayValue: displayValue, clearDisplay: false })

    if (digit !== '.') {

      const i = this.state.currentPositionOnValues
      const newValue = parseFloat(displayValue)
      const values = [...this.state.values]
      values[i] = newValue
      this.setState({ values: values })

    }

  }

  render() {

    return (
      <View style={styles.container}>

        <LinearGradient
          colors={['#4b6cb7', '#182848']}
          style={styles.background}
          start={[1, 1]} end={[0, 0]}
        >
        </LinearGradient>
        <Text style={styles.head}>Calculator</Text>
        <Calculator
          addDigit={() => this.addDigit}
          clear={() => this.clear}
          setOperation={() => this.setOperation}
          displayValue = {this.state.displayValue}
        />
      </View>
    );

  }
}

export default App;

Calculator.js

import React from 'react';
import { View } from 'react-native';
import Interface from './Interface';  

const Calculator = (props) => {

    return (
        <View>
            <Interface 
                addDigit={props.addDigit} 
                clear={props.clear} 
                displayValue={props.displayValue}
                setOperation={props.setOperation}
            />
        </View>
    );
    
}


export default Calculator;

Interface.js

import React from 'react';
import { View, StyleSheet, FlatList, Text } from 'react-native';
import Button from './Button';
import Display from './Display';

const Interface = (props) => {

    return (

        <View style={style.container}>

            <Display value={props.displayValue} />
            <Button label="AC" executeFunction={props.clear} triple />
            <Button label="/" executeFunction={props.setOperation} operation />
            <Button label="7" executeFunction={props.addDigit} />
            <Button label="8" executeFunction={props.addDigit} />
            <Button label="9" executeFunction={props.addDigit} />
            <Button label="*" executeFunction={props.setOperation} operation />
            <Button label="4" executeFunction={props.addDigit} />
            <Button label="5" executeFunction={props.addDigit} />
            <Button label="6" executeFunction={props.addDigit} />
            <Button label="-" executeFunction={props.setOperation} operation />
            <Button label="1" executeFunction={props.addDigit} />
            <Button label="2" executeFunction={props.addDigit} />
            <Button label="3" executeFunction={props.addDigit} />
            <Button label=" " executeFunction={props.setOperation} operation />
            <Button label="0" executeFunction={props.addDigit} double />
            <Button label="." executeFunction={props.addDigit} />
            <Button label="=" executeFunction={props.setOperation} operation />

        </View>

    );

}

const style = StyleSheet.create({

    container: {
        
        width: 400,
        borderRadius: 5,
        overflow: 'hidden',
        position: 'relative',
        justifyContent: 'center',
        flexDirection: 'row',
        flexWrap: 'wrap',
    }

})

export default Interface

Button.js

import React from "react";
import { View, StyleSheet, Text, TouchableHighlight } from "react-native";

const Button = (props) => {

    let classes = 'button '
    classes  = props.operation ? 'operation' : ''
    classes  = props.double ? 'double' : ''
    classes  = props.triple ? 'triple' : ''

    if (props.operation) {

        return (

            <TouchableHighlight onPress={props.executeFunction(props.label)}>

                <View style={[style.button, style.operation]}>

                    <Text style={style.text}>
                        {props.label}
                    </Text>

                </View>
            </TouchableHighlight>

        )

    } else if (props.double) {

        return (

            <TouchableHighlight onPress={props.executeFunction(props.label)}>
                <View style={[style.button, style.double]}>

                    <Text style={style.text}>
                        {props.label}
                    </Text>


                </View>
            </TouchableHighlight>

        )

    } else if (props.triple) {

        return (

            <TouchableHighlight onPress={props.executeFunction()}>
                <View style={[style.button, style.triple]}>

                    <Text style={style.text}>
                        {props.label}
                    </Text>

                </View>
            </TouchableHighlight>

        )

    } else {

        return (

            <TouchableHighlight onPress={props.executeFunction(props.label)} activeOpacity={0.8}>
                <View style={[style.button]}>


                    <Text style={style.text}>
                        {props.label}
                    </Text>


                </View>
            </TouchableHighlight>

        )

    }
}

const style = StyleSheet.create({

    button: {

        width: 100,
        height: 100,
        fontSize: 30,
        backgroundColor: '#f0f0f0',
        outline: 'none',
        textAlign: 'center',
        justifyContent: 'space-evenly',

        borderColor: '#888',
        borderWidth: 1,
        borderStyle: 'solid',

    },

    double: {

        width: 200,

    },

    triple: {

        width: 300,

    },

    operation: {
        backgroundColor: '#fa8231',
        color: '#FFF',
    },

    text: {
        fontSize: 30,
        textAlign: 'center',
        justifyContent: 'space-evenly',
    }

})

export default Button

CodePudding user response:

What is happening?

Currently your code will execute props.executeFunction(props.label) once when the component loads.

This is because the function is immediately invoked when evaluated:

// directly invokes `props.executeFunction` with `props.label` because of brackets `()`
onPress={props.executeFunction(props.label)} 

Because your function returns undefined, this is what subsequently gets passed to the onPress prop.

What's the solution?

If you want to pass props.label then you can pass via an anonymous function instead:

onPress={() => props.executeFunction(props.label)}

The onPress prop will attach the anonymous function to the event. When the event is triggered, the function will be called which will in turn call props.executeFunction passing props.label as desired.

The SyntheticEvent

The onPress function passes a SyntheticEvent when calling the handler. We can see this by logging the event in an anonymous function:

onPress={(event) => console.log(event)} // SyntheticEvent

Therefore, if you pass props.executeFunction as a direct argument to the onPress prop this event is what gets passed:

onPress={props.executeFunction} // passes the event as the argument to `props.executeFunction`

Examples

Invoking inline

This executes the function when the expression is evaluated (once). undefined is returned and passed to onClick so the onClick handler is not added.

function App(props) {
  function foo(bar) {
    console.log(bar);
  }

  return (
    <button onClick={foo("test")}>Test</button>
  );
}

ReactDOM.render(
   <App />,
   document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="react"></div>

Invoking via anonymous function

The onClick handler is passed the anonymous function which will execute on every click.

This is useful if you need to pass arguments to your functions.

function App(props) {
  function foo(bar) {
    console.log(bar);
  }

  return (
    <button onClick={() => foo("test")}>Test</button>
  );
}

ReactDOM.render(
   <App />,
   document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="react"></div>

Passing function as an argument

The event gets passed to the function.

This is useful if you need to access the event.

function App(props) {
  function foo(bar) {
    console.log(bar); // this logs the event as the event is passed
  }

  return (
    <button onClick={foo}>Test</button>
  );
}

ReactDOM.render(
   <App />,
   document.getElementById("react-root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="react-root"></div>

CodePudding user response:

updated your code: https://snack.expo.dev/qw9iqqVxs

App.JS

import { StyleSheet, Text, View } from 'react-native';
import React, { Component } from 'react';
import Calculator from './Calculator';
import { LinearGradient } from 'expo-linear-gradient';

const initialState = {
  displayValue: 0,
  clearDisplay: false,
  previousOperation: null,
  operation: null,
  values: [null, null],
  currentPositionOnValues: 0,
  originalValue: 0,
};

class App extends Component {
  constructor(props) {
    super(props);

    this.state = { ...initialState };
    this.clear = this.clear.bind(this);
    this.addDigit = this.addDigit.bind(this);
  }

  clear() {
    this.setState({ ...initialState });
  };

  addDigit(digit){
    if (digit === '.' && this.state.displayValue.includes('.')) {
      // Prevent double decimals
      return;
    }

    const clearDisplay =
      this.state.displayValue === '0' || this.state.clearDisplay;

    /* 
        Boolean value saying if it's necessary to clear the display
        True if the currentValue display value is 0 or the variable this.state.clearDisplay is set to true
    */

    const currentValue = clearDisplay ? '' : this.state.displayValue;

    /* 
        currentValue shows the 'cleared' value or the display value
    */

    const displayValue = currentValue   digit;

    this.setState({ displayValue: displayValue, clearDisplay: false })

    if (digit !== '.') {
      const i = this.state.currentPositionOnValues;
      const newValue = parseFloat(displayValue)
      const values = [...this.state.values];
      values[i] = newValue;
      this.setState({ values: values });
    }
  };

  render() {
    return (
      <View style={styles.container}>
        <LinearGradient
          colors={['#4b6cb7', '#182848']}
          style={styles.background}
          start={[1, 1]}
          end={[0, 0]}></LinearGradient>
        <Text style={styles.head}>Calculator</Text>
        <Text style={styles.head}>
          {JSON.stringify(this.state.displayValue)}
        </Text>
        <Calculator
          addDigit={(digit) => {
            this.addDigit(digit);
          }}
          clear={(value) => {
            this.clear(value)
          }}
          setOperation={this.setOperation}
          displayValue={this.state.displayValue}
        />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },

  background: {
    background: 'red',
  },

  triple: {
    width: 300,
  },

  operation: {
    backgroundColor: '#fa8231',
    color: '#FFF',
  },

  text: {
    fontSize: 30,
    textAlign: 'center',
    justifyContent: 'space-evenly',
  },
});

export default App;

Button.JS

import React from "react";
import { View, StyleSheet, Text, TouchableHighlight } from "react-native";

const Button = ({operation, double, triple, executeFunction, label}) => {

    let classes = 'button '
    classes  = operation ? 'operation' : ''
    classes  = double ? 'double' : ''
    classes  = triple ? 'triple' : ''

    if (operation) {

        return (

            <TouchableHighlight onPress={()=>executeFunction(label)}>

                <View style={[style.button, style.operation]}>

                    <Text style={style.text}>
                        {label}
                    </Text>

                </View>
            </TouchableHighlight>

        )

    } else if (double) {

        return (

            <TouchableHighlight onPress={()=>executeFunction(label)}>
                <View style={[style.button, style.double]}>

                    <Text style={style.text}>
                        {label}
                    </Text>


                </View>
            </TouchableHighlight>

        )

    } else if (triple) {

        return (

            <TouchableHighlight onPress={()=>executeFunction()}>
                <View style={[style.button, style.triple]}>

                    <Text style={style.text}>
                        {label}
                    </Text>

                </View>
            </TouchableHighlight>

        )

    } else {

        return (

            <TouchableHighlight onPress={()=>executeFunction(label)} activeOpacity={0.8}>
                <View style={[style.button]}>


                    <Text style={style.text}>
                        {label}
                    </Text>


                </View>
            </TouchableHighlight>

        )

    }
}

const style = StyleSheet.create({

    button: {

        width: 100,
        height: 100,
        fontSize: 30,
        backgroundColor: '#f0f0f0',
        outline: 'none',
        textAlign: 'center',
        justifyContent: 'space-evenly',

        borderColor: '#888',
        borderWidth: 1,
        borderStyle: 'solid',

    },

    double: {

        width: 200,

    },

    triple: {

        width: 300,

    },

    operation: {
        backgroundColor: '#fa8231',
        color: '#FFF',
    },

    text: {
        fontSize: 30,
        textAlign: 'center',
        justifyContent: 'space-evenly',
    }

})

export default Button

Calculator

import React from 'react';
import { View } from 'react-native';
import Interface from './Interface';  

const Calculator = ({addDigit, clear, displayValue, setOperation}) => {

    return (
        <View>
            <Interface 
                addDigit={addDigit}
                clear={clear}
                displayValue={displayValue}
                setOperation={setOperation}
            />
        </View>
    );
    
}


export default Calculator;

Interface.JS

import React from 'react';
import { View, StyleSheet, FlatList, Text } from 'react-native';
import Button from './Button';

const Interface = ({clear, addDigit, setOperation}) => {

    return (

        <View style={style.container}>

            <Button label="AC" executeFunction={clear} triple />
            <Button label="/" executeFunction={setOperation} operation />
            <Button label="7" executeFunction={addDigit} />
            <Button label="8" executeFunction={addDigit} />
            <Button label="9" executeFunction={addDigit} />
            <Button label="*" executeFunction={setOperation} operation />
            <Button label="4" executeFunction={addDigit} />
            <Button label="5" executeFunction={addDigit} />
            <Button label="6" executeFunction={addDigit} />
            <Button label="-" executeFunction={setOperation} operation />
            <Button label="1" executeFunction={addDigit} />
            <Button label="2" executeFunction={addDigit} />
            <Button label="3" executeFunction={addDigit} />
            <Button label=" " executeFunction={setOperation} operation />
            <Button label="0" executeFunction={addDigit} double />
            <Button label="." executeFunction={addDigit} />
            <Button label="=" executeFunction={setOperation} operation />

        </View>

    );

}

const style = StyleSheet.create({

    container: {
        
        width: 400,
        borderRadius: 5,
        overflow: 'hidden',
        position: 'relative',
        justifyContent: 'center',
        flexDirection: 'row',
        flexWrap: 'wrap',
    }

})

export default Interface
  • Related