1

i want to display every error messages from Django Rest Api automatically in React frontend. i wanted to test my concept with the signup authentication function and after fixing it i would like to use the concept in every components in fetching data from or into django API.

here is my App.js to register a user just for test:

import React, { useState } from "react";

export default function Signup() {
  const [username, setUsername] = useState("");
  const [email, setEmail] = useState("");
  const [password1, setPassword1] = useState("");
  const [password2, setPassword2] = useState("");
  
  const [user, setUser] = useState("");
  function handleEmail(evt) {
    setEmail(evt.target.value);
  }
  function handleUsername(evt) {
    setUsername(evt.target.value);
  }

  function handlePassword1(evt) {
    setPassword1(evt.target.value);
  }
  function handlePassword2(evt) {
    setPassword2(evt.target.value);
  }
  function handle_signup(evt) {
    evt.preventDefault();
    fetch("http://127.0.0.1:8000/api/v1/rest-auth/registration/", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ username, email, password1, password2 }),
    })
      .then((res) => res.json())
      .then((json) => {
        localStorage.setItem("token", json.key);
        console.log(json);
        setUser(json.username);
      })
      .catch((err) => {
        if(err.res){
          console.log(err.res.username)
          console.log(err.res.email);
          console.log(err.res.password1);
          console.log(err.res.password2);
          
        }else if(err.res){
          console.log(err.res)
          
        }else{
          console.log('Error',err.message)
        }
        console.log(err.config);
      });
  }

  return (
    <form onSubmit={(evt) => handle_signup(evt, setUser())}>
      <label htmlFor="register-username">Username:</label>
      <input
        type="text"
        value={username}
        onChange={handleUsername}
        name="register-username"
        id="register-username"
      />
      <label htmlFor="register-email">Email:</label>
      <input
        type="text"
        value={email}
        onChange={handleEmail}
        name="register-username"
        id="register-username"
      />
      <label htmlFor="register-password1">Password1:</label>
      <input
        type="password1"
        value={password1}
        onChange={handlePassword1}
        name="register-password1"
        id="register-password1"
      />
      <label htmlFor="register-password2">password2:</label>
      <input
        type="password2"
        value={password2}
        onChange={handlePassword2}
        name="register-password2"
        id="register-password2"
      />
      <input type="submit" value="Register" />
    </form>
  );
}

in UseEffect i have tried to show every error message under relevant input boxes which are username, email, password1, password2, i tried to do it by React-hook-form but it will be like inserting error messages from frontend. but i want to show actual error messages from backend. in development tools, when i try upper codes by putting wrong infos in input boxes, it would only show POST: 400 (bad request)

how can i show such error messages under every input boxes like Username exist or email address is invalid, or password must be at least 8 which are typical in Django Rest API's typical error messages ?

FYI: this code can register any user if the input boxes are correctly filled up.

ali
  • 135
  • 1
  • 13

1 Answers1

2

The code below is from my article React Token-Based Authentication to Django REST API Backend. It is using react-bootstrap. It is a simple example with username and password, but you can easily extend it.

If there is a known error (axios docs about handling errors) I check if it has a message for username or password. If yes, then I set an error message for FormControl.Feedback. If you don't want to use react-bootstrap you can just make a small red text inside div and make it visible only if the error message is set (not empty).

// frontend/src/components/SignupReducer.js

// import needed actions
import {
  CREATE_USER_ERROR,
  CREATE_USER_SUBMITTED,
  CREATE_USER_SUCCESS
} from "./SignupTypes";

// define the initial state of the signup store
const initialState = {
  usernameError: "",
  passwordError: "",
  isSubimtted: false
};

// define how action will change the state of the store
export const signupReducer = (state = initialState, action) => {
  switch (action.type) {
    case CREATE_USER_SUBMITTED:
      return {
        usernameError: "",
        passwordError: "",
        isSubimtted: true
      };
    case CREATE_USER_ERROR:
      const errorState = {
        usernameError: "",
        passwordError: "",
        isSubimtted: false
      };
      if (action.errorData.hasOwnProperty("username")) {
        errorState.usernameError = action.errorData["username"];
      }
      if (action.errorData.hasOwnProperty("password")) {
        errorState.passwordError = action.errorData["password"];
      }
      return errorState;
    case CREATE_USER_SUCCESS:
      return {
        usernameError: "",
        passwordError: "",
        isSubimtted: false
      };
    default:
      return state;
  }
}
// frontend/src/components/signup/SignupActions.js

import axios from "axios";
import { toast } from "react-toastify";
import { isEmpty } from "../../utils/Utils";
import {
  CREATE_USER_ERROR,
  CREATE_USER_SUBMITTED,
  CREATE_USER_SUCCESS
} from "./SignupTypes";

export const signupNewUser = userData => dispatch => {
  dispatch({ type: CREATE_USER_SUBMITTED }); // set submitted state
  axios
    .post("/api/v1/users/", userData)
    .then(response => {
      toast.success(
        "Account for " +
          userData.username +
          " created successfully. Please login."
      );
      dispatch({ type: CREATE_USER_SUCCESS });
    })
    .catch(error => {
      if (error.resposne) {
        // The request was made and the server responded with a status code
        // that falls out of the range of 2xx
        toast.error(JSON.stringify(error.response.data));
        dispatch({
          type: CREATE_USER_ERROR,
          errorData: error.response.data
        });
      } else if (error.message) {
        // the error message is available,
        // let's display it on error toast
        toast.error(JSON.stringify(error.message));
      } else {
        // strange error, just show it
        toast.error(JSON.stringify(error));
      }
    });
};
// frontend/src/components/signup/Signup.js file

import React, { Component } from "react";
import { withRouter } from "react-router-dom"; // new import
import { connect } from "react-redux"; // new import
import PropTypes from "prop-types"; // new import
import { Link } from "react-router-dom";
import {
  Container,
  Button,
  Row,
  Col,
  Form,
  FormControl
} from "react-bootstrap";

import { signupNewUser } from "./SignupActions"; // new import

class Signup extends Component {
  constructor(props) {
    super(props);
    this.state = {
      username: "",
      password: ""
    };
  }
  onChange = e => {
    this.setState({ [e.target.name]: e.target.value });
  };

  // update function to call the action
  onSignupClick = () => {
    const userData = {
      username: this.state.username,
      password: this.state.password
    };
    this.props.signupNewUser(userData); // <-- signup new user request
  };

  render() {
    return (
      <Container>
        <Row>
          <Col md="4">
            <h1>Sign up</h1>
            <Form>
              <Form.Group controlId="usernameId">
                <Form.Label>User name</Form.Label>
                <Form.Control
                  isInvalid={this.props.createUser.usernameError}
                  type="text"
                  name="username"
                  placeholder="Enter user name"
                  value={this.state.username}
                  onChange={this.onChange}
                />
                <FormControl.Feedback type="invalid">
                  {this.props.createUser.usernameError}
                </FormControl.Feedback>
              </Form.Group>

              <Form.Group controlId="passwordId">
                <Form.Label>Your password</Form.Label>
                <Form.Control
                  isInvalid={this.props.createUser.passwordError}
                  type="password"
                  name="password"
                  placeholder="Enter password"
                  value={this.password}
                  onChange={this.onChange}
                />
                <Form.Control.Feedback type="invalid">
                  {this.props.createUser.passwordError}
                </Form.Control.Feedback>
              </Form.Group>
            </Form>
            <Button color="primary" onClick={this.onSignupClick}>
              Sign up
            </Button>
            <p className="mt-2">
              Already have account? <Link to="/login">Login</Link>
            </p>
          </Col>
        </Row>
      </Container>
    );
  }
}

// connect action and reducer
// replace 
// export default Signup;
// with code below:

Signup.propTypes = {
  signupNewUser: PropTypes.func.isRequired,
  createUser: PropTypes.object.isRequired
};

const mapStateToProps = state => ({
  createUser: state.createUser
});

export default connect(mapStateToProps, {
  signupNewUser
})(withRouter(Signup));

signup with errors

pplonski
  • 5,023
  • 1
  • 30
  • 34