From the react console I get the error "Unable to load resource: The server responded with status 500 ()" pointing to the url ":8080/api/review-add"
From the Spring Boot backend, I get the error message "not-null property reference null or transient value: com.reactspringboot.bookclubbackend.entity.Review.account"
The problem is when doing a POST request in Postman I don't get an error and the object is posted to the database.
This is the file from the Spring Boot backend:
Forum.java
@Entity @Table(name="Forum") public class Forum { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="id") private long id; @Column(name="book_name") private String bookName; @Column(name="book_image") private String bookImage; @Lob @Column(name="description") private String description; @OneToMany(mappedBy = "forum", cascade = CascadeType.ALL) private List<Review> reviews; public Forum() { } public Forum(String bookName, String bookImage, String description, List<Review> reviews) { this.bookName = bookName; this.bookImage = bookImage; this.description = description; this.reviews = reviews; } public long getId() { return id; } public String getBookName() { return bookName; } public void setBookName(String bookName) { this.bookName = bookName; } public String getBookImage() { return bookImage; } public void setBookImage(String bookImage) { this.bookImage = bookImage; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } // using 'JsonProperty' to prevent infinite recursion @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) public List<Review> getReviews() { return reviews; } public void setReviews(List<Review> reviews) { this.reviews = reviews; } }
Account.java
@Entity @Table(name="account") public class Account { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="id") private long id; @Column(name="account_name") private String accountName; @Column(name="password") private String password; @Column(name="email") private String email; @Column(name="profile_image") private String profileImage; @Column(name="role") private String role; @Column(name="date_joined") @CreationTimestamp private Date dateJoined; @OneToMany(mappedBy = "account", cascade = CascadeType.ALL) private List<Review> reviews; public Account() { } public Account(String accountName, String password, String email, String profileImage, String role, Date dateJoined, List<Review> reviews) { this.accountName = accountName; this.password = password; this.email = email; this.profileImage = profileImage; this.role = role; this.dateJoined = dateJoined; this.reviews = reviews; } public long getId() { return id; } public String getAccountName() { return accountName; } public void setAccountName(String accountName) { this.accountName = accountName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getProfileImage() { return profileImage; } public void setProfileImage(String profileImage) { this.profileImage = profileImage; } public String getRole() { return role; } public void setRole(String role) { this.role = role; } public Date getDateJoined() { return dateJoined; } public void setDateJoined(Date dateJoined) { this.dateJoined = dateJoined; } // using 'JsonProperty' to prevent infinite recursion @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) public List<Review> getReviews() { return reviews; } public void setReviews(List<Review> reviews) { this.reviews = reviews; } }
Review.java
@Entity @Table(name="review") public class Review { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="id") private long id; @Column(name="date_posted") @CreationTimestamp private Date datePosted; @Lob @Column(name="comment") private String comment; @Column(name="star_rating") private double starRating; //@JsonIgnore @ManyToOne @JoinColumn(name = "account_id",nullable = false, insertable = true, updatable = true) private Account account; //@JsonIgnore @ManyToOne @JoinColumn(name = "forum_id",nullable = false, insertable = true, updatable = true) private Forum forum; public Review() { } public Review(Date datePosted, String comment, double starRating, Account account, Forum forum) { this.datePosted = datePosted; this.comment = comment; this.starRating = starRating; this.account = account; this.forum = forum; } public long getId() { return id; } public Date getDatePosted() { return datePosted; } public void setDatePosted(Date datePosted) { this.datePosted = datePosted; } public String getComment() { return comment; } public void setComment(String comment) { this.comment = comment; } public double getStarRating() { return starRating; } public void setStarRating(double starRating) { this.starRating = starRating; } public Account getAccount() { return account; } public void setAccount(Account account) { this.account = account; } public Forum getForum() { return forum; } public void setForum(Forum forum) { this.forum = forum; } }
ReviewRepository.java
@CrossOrigin(origins = "http://localhost:3000") public interface ReviewRepository extends JpaRepository<Review, Long> { @Query(value = "SELECT * FROM Review r where r.forum_id = :forum_id", nativeQuery = true) public List<Review> getReviewsUsingForumId(@Param("forum_id") Long forum_id); }
ReviewController.java
@CrossOrigin(origins = "http://localhost:3000") @RestController @RequestMapping("/api/") public class ReviewController { @Autowired private ReviewRepository reviewRepository; // create review rest api //@CrossOrigin(origins = "http://localhost:3000") @PostMapping("/review-add") public Review createReview(@RequestBody Review review) { return reviewRepository.save(review); } @GetMapping("/forum-reviews/{forumId}") public List<Review> getReviewsByForumId(@RequestBody @PathVariable Long forumId) { return reviewRepository.getReviewsUsingForumId(forumId); } }
From React frontend
PostComponent.js
function PostComponent() { // right now only using static data for testing const [review, setReview] = useState({datePosted: "2023-07-26", comment: "", starRating: 5.0, account: { id: 1 }, forum: { id: 1 }}); const postURL = "http://localhost:8080/api/review-add"; function changeHandler (event) { setReview({comment: event.target.value}); } function submit(event) { event.preventDefault(); axios.post(postURL, review) } useEffect(() => { console.log(review.comment) }, [review]); return ( <div> <div className="container"> <h2>Post Review</h2> <form onSubmit={(event) => submit(event)}> <div className="form-group"> <label>Comment</label> <input placeholder="Commet here" name="comment" className="form=control" value={review.comment} onChange={changeHandler}/> </div> <button>Submit</button> </form> </div> </div> ) } export default PostComponent;
In the postman body I select raw and set to JSON and when I use this POST request to the "review" entity it works:
{ "datePosted": "2023-07-26", "comment": "From postman.", "starRating": 5.0, "account": { "id": 1 }, "forum": { "id": 1 } }
The forum with id 1 and the account with id 1 exist in the database.
If anyone can help me or point me to a solution to this problem I would be very grateful. I've been stuck with this problem for days and I'm running out of ideas.
If the server expects to receive the entire payload:
The server will not receive it after you perform the following operations:
The server will only receive:
When you only want to update one property in the state, the update still needs to include the rest of the object to preserve the entire object:
or:
BTW...your browser debugging tools can help you narrow down to this exact problem. You can observe the payload in the AJAX request being sent, see that it's not what you expect, and immediately rule out all server-side code as the cause of the problem and start focusing on client-side debugging.