So what I am basically doing is, I have an API of call activities and its own details.
I have to archive a call and in that API of calls, each call has a field called “is_archived”
I need to be able to update the API using a click of a button to archive a call. (So basically change the field of “is_archived” from “false” to “true” once the button is clicked)
And once that call has been archived, it shouldnt render nor be displayed on the application anymore.
I’m getting a “Failed to load resource: the server responded with a status of 400 (Bad Request)” with my code and I’m sure I’m doing something wrong, I just cant spot it.
Thank you!
Here is my code so far:
App.jsx
import React, { Component} from 'react'; import { ActivityFeed } from './components/activity-feed/activity-feed.component.jsx'; import Header from './Header.jsx'; class App extends Component { constructor() { super(); this.state = { calls: [], showMessage: false, is_archived: false }; } componentDidMount() { fetch('https://aircall-job.herokuapp.com/activities') .then(response => response.json()) .then(activities => this.setState({ calls: activities })) document.getElementById("reset").disabled = true; } handleArchive = event => { this.setState({calls: []}); this.setState({ showMessage: true }); document.getElementById("archive").disabled = true; document.getElementById("reset").disabled = false; }; handleReset = event => { this.componentDidMount(); this.setState({ showMessage: false }); document.getElementById("reset").disabled = true; document.getElementById("archive").disabled = false; }; render() { const { calls, showMessage } = this.state; console.log(calls); return ( <div className='App'> <Header/> <ActivityFeed calls={calls} /> <button type="button" className="archive-btn" id="archive" onClick={this.handleArchive}>Archive All Calls</button> {showMessage && <p>All calls have been archived</p>} <button type="button" className="reset-btn" id="reset" onClick={this.handleReset}>Reset Archived Calls</button> </div> ); }; } export default App;
Activity.component.jsx
import React from 'react'; import './activity-detail.styles.css'; import missed from '../../resources/images/missed.svg'; import answered from '../../resources/images/answered.svg'; import voicemail from '../../resources/images/voicemail.svg'; function formatDate(date) { var localDate = new Date(date); return localDate.toDateString().split(' ').slice(1).join(' '); } function formatTime(time) { var localTime = new Date(time); return localTime.toLocaleTimeString().replace(/(.*)Dd+/, '$1');; } function callType(type) { if (type === "missed") { return <img src={missed} alt="missed" className="call-icon"/> } else if (type === "answered") { return <img src= {answered} alt="answered" className="call-icon"/> } else return <img src= {voicemail} alt="voicemail" className="call-icon"/> } function archiveCall(id) { fetch(`https://aircall-job.herokuapp.com/activities/${id}`, { mode: 'no-cors', method: "POST", headers: { 'Accept' : 'application/json', "Content-Type": "application/json" }, body: JSON.stringify({ is_archived: true }), }) } export const Activity = props => ( <div className='activity-container'> <p> Date {formatDate(props.call.created_at)} </p> <p> Time {formatTime(props.call.created_at)} </p> <p> From {props.call.from} </p> <p> To {props.call.to} </p> <p> Via {props.call.via} </p> <p> Call type {callType(props.call.call_type)} </p> <button type="button" className="archive-call" id="archive-call" onClick={archiveCall(props.call.id)} >Archive call</button> </div> );
ActivityFeed.component.jsx
import React from 'react'; import { Activity } from '../activity-detail/activity-detail.component'; import './activity-feed.styles.css'; export const ActivityFeed = props => ( <div className='activity-feed'> {props.calls.map(calls => ( <Activity key={calls.id} call={calls}/> ))} </div> );
Advertisement
Answer
For some reason, I’m not sure why, but if you have no-cors mode set on your fetch POST request, the content-type on your request gets changed to text/plain.
Fetch API – Content-Type is sent as text/plain when it’s set to application/json
I was able to figure this out by duplicating your request in a sandbox, right-clicking your request in Chrome’s network tab, and choosing “Copy as cURL”. Then import it into Postman so I could replicate the exact request. There I could see it was converted into a plain-text content instead of a JSON body as intended.
Of course you can also see these things in the requests inside the network tab, but sometimes putting it inside Postman makes it stand out more clearly.
So the solution is to simply omit the “no-cors” option and your request works fine in React.
fetch(`https://aircall-job.herokuapp.com/activities/${id}`, { method: "POST", headers: { Accept: "application/json", "Content-Type": "application/json" }, body: JSON.stringify({ is_archived: true }) });
https://codesandbox.io/s/cranky-khayyam-qm2rz?file=/src/App.js