2

I'm stuck with passing data returned from a mutation to handle form validation in Reactjs. This pattern is not limited to only form validation but to other components which share similar requirements where something is mutated/queried and the response data is used to update the UI only.

Concept:

<LoginForm/> component renders the login form and commits the loginMutation with form data. Validation happens on the backend, if there are any errors catched by the validation the mutation will resolve with field errors which contains key-value pairs where key represents the form field and value describes what's wrong with the value. The idea is to pass the errors object to the <LoginForm/> so that the component can render/update to display the errors in the UI.

errors: { 
  username: "Username must not be empty", 
  password: "Password must not be empty"
}

Challenge:

In what way should resolved data (errors) be passed to the <LoginForm/>? The most straightforward, hammer-nail method would be to pass a callback function to the loginMutation from the <LoginForm/> and call it inside of the onComplete() with the response object. Something like this:

onCompleted: (response, errors) => { setLoginFormState(resonse.errors) }

Then in the <LoginForm/> there would be

setLoginFormState(validationState){ 
    this.setState({errors: validationState })
}

And finally we can update the UI accordingly in the <LoginForm/> like this

render(){
  const { errors } = this.state;
  const usernameClass = errors.username ? 'danger' : 'primary';
  const passwordClass = errors.password ? 'danger' : 'primary';
  return (
    <div>
      <input name="username" className={usernameClass} />
     <input name="username" className={passwordClass} />
    </div>
  )

}

I'm searching for a less imperative approach that produces less spaghetti.

Also I'm looking forward to the announced schema extensions feature which is currently missing docs. It seems as if it's purpose is to solve this problem by allowing us to mix local state(UI state for instance) with the persistent data fetched from the queries/mutations.

Ante Gulin
  • 2,022
  • 2
  • 15
  • 16

1 Answers1

-1

React-Relay-Rebind is a component-scope state management for Relay modern & React.

I've created this library to provide a declarative API for managing local state returned by the Relay mutations. The library is currently under active development. Opening new issues and submitting PR's is welcomed!

React-Relay-Rebind allows you to bind mutations to a component. Binding means that the component will recieve state returned by the mutation as props.

Usage example

import { rebind } from 'react-relay-rebind';
import { loginMutation } from './loginMutation';

class MyComponent extends React.component {

  handleSubmit(){
    this.props.mutations.login(this.props.relay, this.state);
  }

  handleFieldChange(fieldName){
    return (e) => {
      // Login mutation stateProxy
      const { login } = this.props;

      this.setState({ [fieldName]: e.target.value() });

      // Set login mutation to initial state
      this.login.resetState();
    }
  }

  render(){
    const { errors } = this.props.login.state;
    const usernameClassname = errors.username ? 'has-error' : '';
    const passwordClassname = errors.password ? 'has-error' : '';

    return (
       <div>
        <input
          className={usernameClassname}
          name="username"
          type="text"
          onChange={this.handleFieldChange('username')}
          value={username} />
        <br/>
        <input
          className={passwordClassname}
          name="password"
          type="password"
          onChange={this.handleFieldChange('password')}
          value={password} />
        <br/>
        <input type="submit" onClick={this.handleSubmit} value="Login"/>
      </div>
  }
}

const mutations = {
  login: {
    mutation: LoginMutation,
    initialState: {
      errors: {
        username: null,
        password: null,
      },
    },
  },
};
export default rebind(mutations)(MyComponent);
Ante Gulin
  • 2,022
  • 2
  • 15
  • 16
  • I think I'd prefer the `onCompleted` and `onError` explicit callbacks used to commit a mutation when dealing with this kind of problem. Perhaps you'd find it less imperative by writing `onCompleted: this._setStateFromMutation`... And `_setStateFromMutation = (response) => { ... }` – hisa_py Nov 24 '17 at 14:26