我正在研究Pro MERN Stack:使用Mongo,Express,React和Node书籍进行全栈Web应用程序开发,每当我尝试返回首页时,都会遇到CORS政策错误。看来错误来自我的graphqlFetch文件,但我不确定是什么问题。任何见解都将非常有帮助!
CORS政策错误消息
GraphQLFetch错误 1: 我的GraphQLFetch代码:
import fetch from 'isomorphic-fetch';
const dateRegex = new RegExp('^\\d\\d\\d\\d-\\d\\d-\\d\\d');
function jsonDateReviver(key, value) {
if (dateRegex.test(value)) return new Date(value);
return value;
}
export default async function
graphQLFetch(query, variables = {}, showError = null, cookie = null) {
const apiEndpoint = (__isBrowser__) // eslint-disable-line no-undef
? window.ENV.UI_API_ENDPOINT
: process.env.UI_SERVER_API_ENDPOINT;
try {
const headers = { 'Content-Type': 'application/json' };
if (cookie) headers.Cookie = cookie;
const response = await fetch(apiEndpoint, {
method: 'POST',
credentials: 'include',
headers,
body: JSON.stringify({ query, variables }),
});
const body = await response.text();
const result = JSON.parse(body, jsonDateReviver);
if (result.errors) {
const error = result.errors[0];
if (error.extensions.code === 'BAD_USER_INPUT') {
const details = error.extensions.exception.errors.join('\n ');
if (showError) showError(`${error.message}:\n ${details}`);
} else if (showError) {
showError(`${error.extensions.code}: ${error.message}`);
}
}
return result.data;
} catch (e) {
if (showError) showError(`Error in sending data to server: ${e.message}`);
return null;
}
}
我的IssueEdit文件
import React from 'react';
import { Link } from 'react-router-dom';
import { LinkContainer } from 'react-router-bootstrap';
import {
Col, Panel, Form, FormGroup, FormControl, ControlLabel,
ButtonToolbar, Button, Alert,
} from 'react-bootstrap';
import graphQLFetch from './graphQLFetch.js';
import NumInput from './NumInput.jsx';
import DateInput from './DateInput.jsx';
import TextInput from './TextInput.jsx';
import Toast from './Toast.jsx';
import store from './store.js';
export default class IssueEdit extends React.Component {
static async fetchData(match, search, showError) {
const query = `query issue($id: Int!) {
issue(id: $id) {
id title status owner
effort created due description
}
}`;
const { params: { id } } = match;
const result = await graphQLFetch(query, { id }, showError);
return result;
}
constructor() {
super();
const issue = store.initialData ? store.initialData.issue : null;
delete store.initialData;
this.state = {
issue,
invalidFields: {},
showingValidation: false,
toastVisible: false,
toastMessage: '',
toastType: 'success',
};
this.onChange = this.onChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.onValidityChange = this.onValidityChange.bind(this);
this.dismissValidation = this.dismissValidation.bind(this);
this.showValidation = this.showValidation.bind(this);
this.showSuccess = this.showSuccess.bind(this);
this.showError = this.showError.bind(this);
this.dismissToast = this.dismissToast.bind(this);
}
componentDidMount() {
const { issue } = this.state;
if (issue == null) this.loadData();
}
componentDidUpdate(prevProps) {
const { match: { params: { id: prevId } } } = prevProps;
const { match: { params: { id } } } = this.props;
if (id !== prevId) {
this.loadData();
}
}
onChange(event, naturalValue) {
const { name, value: textValue } = event.target;
const value = naturalValue === undefined ? textValue : naturalValue;
this.setState(prevState => ({
issue: { ...prevState.issue, [name]: value },
}));
}
onValidityChange(event, valid) {
const { name } = event.target;
this.setState((prevState) => {
const invalidFields = { ...prevState.invalidFields, [name]: !valid };
if (valid) delete invalidFields[name];
return { invalidFields };
});
}
async handleSubmit(e) {
e.preventDefault();
this.showValidation();
const { issue, invalidFields } = this.state;
if (Object.keys(invalidFields).length !== 0) return;
const query = `mutation issueUpdate(
$id: Int!
$changes: IssueUpdateInputs!
) {
issueUpdate(
id: $id
changes: $changes
) {
id title status owner
effort created due description
}
}`;
const { id, created, ...changes } = issue;
const data = await graphQLFetch(query, { changes, id }, this.showError);
if (data) {
this.setState({ issue: data.issueUpdate });
this.showSuccess('Updated issue successfully');
}
}
async loadData() {
const { match } = this.props;
const data = await IssueEdit.fetchData(match, null, this.showError);
this.setState({ issue: data ? data.issue : {}, invalidFields: {} });
}
showValidation() {
this.setState({ showingValidation: true });
}
dismissValidation() {
this.setState({ showingValidation: false });
}
showSuccess(message) {
this.setState({
toastVisible: true, toastMessage: message, toastType: 'success',
});
}
showError(message) {
this.setState({
toastVisible: true, toastMessage: message, toastType: 'danger',
});
}
dismissToast() {
this.setState({ toastVisible: false });
}
render() {
const { issue } = this.state;
if (issue == null) return null;
const { issue: { id } } = this.state;
const { match: { params: { id: propsId } } } = this.props;
if (id == null) {
if (propsId != null) {
return <h3>{`Issue with ID ${propsId} not found.`}</h3>;
}
return null;
}
const { invalidFields, showingValidation } = this.state;
let validationMessage;
if (Object.keys(invalidFields).length !== 0 && showingValidation) {
validationMessage = (
<Alert bsStyle="danger" onDismiss={this.dismissValidation}>
Please correct invalid fields before submitting.
</Alert>
);
}
const { issue: { title, status } } = this.state;
const { issue: { owner, effort, description } } = this.state;
const { issue: { created, due } } = this.state;
const { toastVisible, toastMessage, toastType } = this.state;
return (
<Panel>
<Panel.Heading>
<Panel.Title>{`Editing issue: ${id}`}</Panel.Title>
</Panel.Heading>
<Panel.Body>
<Form horizontal onSubmit={this.handleSubmit}>
<FormGroup>
<Col componentClass={ControlLabel} sm={3}>Created</Col>
<Col sm={9}>
<FormControl.Static>
{created.toDateString()}
</FormControl.Static>
</Col>
</FormGroup>
<FormGroup>
<Col componentClass={ControlLabel} sm={3}>Status</Col>
<Col sm={9}>
<FormControl
componentClass="select"
name="status"
value={status}
onChange={this.onChange}
>
<option value="New">New</option>
<option value="Assigned">Assigned</option>
<option value="Fixed">Fixed</option>
<option value="Closed">Closed</option>
</FormControl>
</Col>
</FormGroup>
<FormGroup>
<Col componentClass={ControlLabel} sm={3}>Owner</Col>
<Col sm={9}>
<FormControl
componentClass={TextInput}
name="owner"
value={owner}
onChange={this.onChange}
key={id}
/>
</Col>
</FormGroup>
<FormGroup>
<Col componentClass={ControlLabel} sm={3}>Effort</Col>
<Col sm={9}>
<FormControl
componentClass={NumInput}
name="effort"
value={effort}
onChange={this.onChange}
key={id}
/>
</Col>
</FormGroup>
<FormGroup validationState={
invalidFields.due ? 'error' : null
}
>
<Col componentClass={ControlLabel} sm={3}>Due</Col>
<Col sm={9}>
<FormControl
componentClass={DateInput}
onValidityChange={this.onValidityChange}
name="due"
value={due}
onChange={this.onChange}
key={id}
/>
<FormControl.Feedback />
</Col>
</FormGroup>
<FormGroup>
<Col componentClass={ControlLabel} sm={3}>Title</Col>
<Col sm={9}>
<FormControl
componentClass={TextInput}
size={50}
name="title"
value={title}
onChange={this.onChange}
key={id}
/>
</Col>
</FormGroup>
<FormGroup>
<Col componentClass={ControlLabel} sm={3}>Description</Col>
<Col sm={9}>
<FormControl
componentClass={TextInput}
tag="textarea"
rows={4}
cols={50}
name="description"
value={description}
onChange={this.onChange}
key={id}
/>
</Col>
</FormGroup>
<FormGroup>
<Col smOffset={3} sm={6}>
<ButtonToolbar>
<Button bsStyle="primary" type="submit">Submit</Button>
<LinkContainer to="/issues">
<Button bsStyle="link">Back</Button>
</LinkContainer>
</ButtonToolbar>
</Col>
</FormGroup>
<FormGroup>
<Col smOffset={3} sm={9}>{validationMessage}</Col>
</FormGroup>
</Form>
</Panel.Body>
<Panel.Footer>
<Link to={`/edit/${id - 1}`}>Prev</Link>
{' | '}
<Link to={`/edit/${id + 1}`}>Next</Link>
</Panel.Footer>
<Toast
showing={toastVisible}
onDismiss={this.dismissToast}
bsStyle={toastType}
>
{toastMessage}
</Toast>
</Panel>
);
}
}
我的IssueList文件:
import React from 'react';
import URLSearchParams from 'url-search-params';
import { Panel } from 'react-bootstrap';
import IssueFilter from './IssueFilter.jsx';
import IssueTable from './IssueTable.jsx';
import IssueDetail from './IssueDetail.jsx';
import graphQLFetch from './graphQLFetch.js';
import withToast from './withToast.jsx';
import store from './store.js';
class IssueList extends React.Component {
static async fetchData(match, search, showError) {
const params = new URLSearchParams(search);
const vars = { hasSelection: false, selectedId: 0 };
if (params.get('status')) vars.status = params.get('status');
const effortMin = parseInt(params.get('effortMin'), 10);
if (!Number.isNaN(effortMin)) vars.effortMin = effortMin;
const effortMax = parseInt(params.get('effortMax'), 10);
if (!Number.isNaN(effortMax)) vars.effortMax = effortMax;
const { params: { id } } = match;
const idInt = parseInt(id, 10);
if (!Number.isNaN(idInt)) {
vars.hasSelection = true;
vars.selectedId = idInt;
}
const query = `query issueList(
$status: StatusType
$effortMin: Int
$effortMax: Int
$hasSelection: Boolean!
$selectedId: Int!
) {
issueList(
status: $status
effortMin: $effortMin
effortMax: $effortMax
) {
id title status owner
created effort due
}
issue(id: $selectedId) @include (if : $hasSelection) {
id description
}
}`;
const data = await graphQLFetch(query, vars, showError);
return data;
}
constructor() {
super();
const issues = store.initialData ? store.initialData.issueList : null;
const selectedIssue = store.initialData
? store.initialData.issue
: null;
delete store.initialData;
this.state = {
issues,
selectedIssue,
};
this.closeIssue = this.closeIssue.bind(this);
this.deleteIssue = this.deleteIssue.bind(this);
}
componentDidMount() {
const { issues } = this.state;
if (issues == null) this.loadData();
}
componentDidUpdate(prevProps) {
const {
location: { search: prevSearch },
match: { params: { id: prevId } },
} = prevProps;
const { location: { search }, match: { params: { id } } } = this.props;
if (prevSearch !== search || prevId !== id) {
this.loadData();
}
}
async loadData() {
const { location: { search }, match, showError } = this.props;
const data = await IssueList.fetchData(match, search, showError);
if (data) {
this.setState({ issues: data.issueList, selectedIssue: data.issue });
}
}
async closeIssue(index) {
const query = `mutation issueClose($id: Int!) {
issueUpdate(id: $id, changes: { status: Closed }) {
id title status owner
effort created due description
}
}`;
const { issues } = this.state;
const { showError } = this.props;
const data = await graphQLFetch(query, { id: issues[index].id },
showError);
if (data) {
this.setState((prevState) => {
const newList = [...prevState.issues];
newList[index] = data.issueUpdate;
return { issues: newList };
});
} else {
this.loadData();
}
}
async deleteIssue(index) {
const query = `mutation issueDelete($id: Int!) {
issueDelete(id: $id)
}`;
const { issues } = this.state;
const { location: { pathname, search }, history } = this.props;
const { id } = issues[index];
const { showSuccess, showError } = this.props;
const data = await graphQLFetch(query, { id }, showError);
if (data && data.issueDelete) {
this.setState((prevState) => {
const newList = [...prevState.issues];
if (pathname === `/issues/${id}`) {
history.push({ pathname: '/issues', search });
}
newList.splice(index, 1);
return { issues: newList };
});
showSuccess(`Deleted issue ${id} successfully.`);
} else {
this.loadData();
}
}
render() {
const { issues } = this.state;
if (issues == null) return null;
const { selectedIssue } = this.state;
return (
<React.Fragment>
<Panel>
<Panel.Heading>
<Panel.Title toggle>Filter</Panel.Title>
</Panel.Heading>
<Panel.Body collapsible>
<IssueFilter />
</Panel.Body>
</Panel>
<IssueTable
issues={issues}
closeIssue={this.closeIssue}
deleteIssue={this.deleteIssue}
/>
<IssueDetail issue={selectedIssue} />
</React.Fragment>
);
}
}
const IssueListWithToast = withToast(IssueList);
IssueListWithToast.fetchData = IssueList.fetchData;
export default IssueListWithToast;
答案 0 :(得分:0)
您必须在graphql服务器上允许交叉原点。出于安全原因,浏览器不允许跨源请求ajax请求。因此,可以说您在localhost:3000上,而服务器在localhost:8000上运行,因此这两个源不同。
因此在Express服务器上使用cors模块,并允许Access-Control-Allow-Origin为“ *”