1

I had 5 screens with the same styles and layout, but some text and button logic difference.

I tried to keep everything in one component, passing there the name of the component I need to mimic, but it grew up in nested if/else all around, what made the code very intricate.

What is lesser of these two evils and what should I do: duplicate the components in favor of simplicity or keep them in one place and lose the readability?

Here's the "all in one" component

const Pin = props => {
  const {
    navigation: { navigate, getParam },
    loading,
    savePin,
    signIn,
    toggleModal,
  } = props

  const [pin, setPin] = useState('')
  const isInSignInStack = getParam('isInSignInStack') ? 'isInSignInStack' : false
  const isConfirmPinSignUp = getParam('isConfirmPinSignUp') ? 'isConfirmPinSignUp' : false
  const isChangePin = getParam('isChangePin') ? 'isChangePin' : false
  const isEnterNewPin = getParam('isEnterNewPin') ? 'isEnterNewPin' : false
  const isConfirmNewPin = getParam('isConfirmNewPin') ? 'isConfirmNewPin' : false
  const newPin = getParam('newPin')

  const handleSavePin = () => savePin(pin).then(() => navigate('ConfirmPinSignUp'))

  const navigateHome = () => navigate('Home')

  const handleAuthenticate = () =>
    compose(
      then(navigateHome),
      then(signIn),
      savePin
    )(pin)

  const validatePin = () =>
      isConfirmNewPin
        ? equals(newPin, pin)
          ? savePin(pin).then(() => navigate('SuccessPinChange'))
          : toggleModal('pin isn't match')
        : getPin().then(({ password }) =>
            equals(password, pin)
              ? navigate(isChangePin ? 'ConfirmNewPin' : 'Success', { ...(isChangePin ? { newPin: pin } : {}) })
              : toggleModal('pin isn't match')
      )

  const textObj = {
    isInSignInStack: 'Enter your pin',
    isConfirmPinSignUp: 'Enter your pin once again',
    isChangePin: 'Enter your old pin',
    isEnterNewPin: 'Enter the new pin',
    isConfirmNewPin: 'Enter the new pin once again',
  }

  return (
    <Container style={styles.container}>
      <Content scrollEnabled={false} contentContainerStyle={styles.content}>
        <Text style={styles.headerText}>
          {pathOr(
            'Come up with the new pin',
            isInSignInStack || isConfirmPinSignUp || isChangePin || isEnterNewPin || isConfirmNewPin,
            textObj
          )}
        </Text>
        <View style={styles.inputView}>
          <CodeInput />
        </View>
        {isConfirmPinSignUp || (
          <View style={styles.aknowledgementView}>
            {isInSignInStack
               ? <Text style={styles.text} onPress={handleForgotPassword}>
                   FORGOT PIN
                 </Text>
               : isEnterNewPin && (
                <>
                  <Text style={styles.greenText}>Attention! Don't use your old pin</Text>
                  <Text style={styles.greenText}>codes or passwords, come up with the new one</Text>
                </>
              )}
          </View>
        )}
        <Button
          style={isEmpty(pin) ? styles.btnDisabled : styles.btn}
          onPress={() =>
              isInSignInStack
                ? handleAuthenticate
                : anyTrue(isConfirmPinSignUp, isChangePin) ? validatePin : handleSavePin
          }
          disabled={anyTrue(isEmpty(pin), loading)}
        >
          {loading ? <Spinner color="black" /> : <Text style={styles.btnText}>Next</Text>}
        </Button>
      </Content>
    </Container>
  )
}

Pin.navigationOptions = ({ navigation: { getParam } }) => {
  const isInSignInStack = getParam('isInSignInStack')
  const isChangePin = getParam('isChangePin')
  const isEnterNewPin = getParam('isEnterNewPin')

  return {
    title: isInSignInStack ? 'SignIn' : anyTrue(isChangePin, isEnterNewPin) ? 'Change PIN' : 'Register'
  }
}

const styles = StyleSheet.create({
  //
})

Pin.propTypes = {
  navigation: PropTypes.shape({
    navigate: PropTypes.func,
    getParam: PropTypes.func,
  }).isRequired,
  loading: PropTypes.bool.isRequired,
  savePin: PropTypes.func.isRequired,
  toggleModal: PropTypes.func.isRequired,
  signIn: PropTypes.func.isRequired,
}

const mapStateToProps = compose(
  pick(['loading']),
  path(['user'])
)

const mapDispatchToProps = {
  savePin,
  signIn,
  toggleModal,
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Pin)
5
  • This is highly dependent on what is being duplicated across your components Commented Oct 15, 2019 at 20:39
  • Checkout the Factory Pattern. Perhaps, its what you are searching for. Commented Oct 15, 2019 at 20:42
  • @tic Hi! Everything is identical, but the text and the logic that the button is handling is different Commented Oct 15, 2019 at 20:46
  • "some text and button logic difference" These should be passed in as props for the text to display and the click handlers for the buttons. Commented Oct 15, 2019 at 21:41
  • @Code-Apprentice Hi! How does it solve the need to distinguish between what data to pass in as props? I still need if/else for this. Can you please check out the code I pinned? Commented Oct 15, 2019 at 21:52

2 Answers 2

3

Make a generic button component that takes a click handler and text as a prop then simply pass the values as props:

for instance if you have some button component like:

export const Button = ({ children, handler }) =>
  <button onPress={handler}>
    {children}
  </button>;

Then you could use it like

<Button handler={this.yourClickHandler} >{"Some Text"}</Button>
Sign up to request clarification or add additional context in comments.

6 Comments

@Jake Thank you! But it doesn't solve the need in if/else. It will truly make the code a little more concise but won't change it too much
@Makar Can you explain what the ternary statement is being used for? It seems it being used for routing. If this is the case it shouldn't be handled inside the button component
@Jake Depending on the route I'm in, the ternary handles what action should be dispatched: authenticate and then navigate to "Home" screen or validate/save pin and navigate to "Success" screen. Can you explain why button shouldn't handle navigation and do you think that the navigation logic is the root of my problem?
@Makar it seems you need to step back one component. By that I mean your main component for each screen should have the click handler defined in it and if its using this Pin component then that handler will be passed to it and then passed down to the button.
@Makar so for instance if you are in the login screen you will pass this Pin component the handleAuthenticate function as a prop and then the pass that prop to to the button. Therefore whatever handler you pass the Pin component, that will be set as the button logic
|
0

The answer is that you should not couple the components which belong to different use cases as they will change for a different reasons and at different times, even though they look identically now they will change later. Don't write the "super component" that cover all the use cases, because it will become a mess very quickly

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.