I'm pretty new to javascript, and I'm trying to create a page to return orders for an e-commerce website that I'm creating. I want a customer to be able to start a return when they click the Return Order button on their orders history page. When I click on the Return Order button I'm getting a POST http://localhost:3000/api/devolucions 400 (Bad Request). This post is created in the backend in devolucionRoute.js and called in the frontend in devolucionActions.js. I'm unsure why I am getting this error, and I would really appreciate any help or guidance on how to fix this issue.
Thank you!
Note: devolucion is the name that I used in place of return
devolucionActions.js
import Axios from 'axios';
import {
DEVOLUCION_CREATE_FAIL,
DEVOLUCION_CREATE_REQUEST,
DEVOLUCION_CREATE_SUCCESS,
DEVOLUCION_DETAILS_FAIL,
DEVOLUCION_DETAILS_REQUEST,
DEVOLUCION_DETAILS_SUCCESS,
DEVOLUCION_SUMMARY_REQUEST,
DEVOLUCION_SUMMARY_SUCCESS,
} from '../constants/devolucionConstants';
export const createDevolucion = (devolucion) => async (dispatch, getState) => {
dispatch({ type: DEVOLUCION_CREATE_REQUEST, payload: devolucion });
try {
const {
userSignin: { userInfo },
} = getState();
const { data } = await Axios.post('/api/devolucions', devolucion, {
headers: {
Authorization: `Bearer ${userInfo.token}`,
},
});
dispatch({ type: DEVOLUCION_CREATE_SUCCESS, payload: data.devolucion });
} catch (error) {
dispatch({
type: DEVOLUCION_CREATE_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
export const detailsDevolucion = (devolucionId) => async (dispatch, getState) => {
dispatch({ type: DEVOLUCION_DETAILS_REQUEST, payload: devolucionId });
const {
userSignin: { userInfo },
} = getState();
try {
const { data } = await Axios.get(`/api/devolucions/${devolucionId}`, {
headers: { Authorization: `Bearer ${userInfo.token}` },
});
dispatch({ type: DEVOLUCION_DETAILS_SUCCESS, payload: data });
} catch (error) {
const message =
error.response && error.response.data.message
? error.response.data.message
: error.message;
dispatch({ type: DEVOLUCION_DETAILS_FAIL, payload: message });
}
};
export const summaryDevolucion = () => async (dispatch, getState) => {
dispatch({ type: DEVOLUCION_SUMMARY_REQUEST });
const {
userSignin: { userInfo },
} = getState();
try {
const { data } = await Axios.get('/api/devolucions/summary', {
headers: { Authorization: `Bearer ${userInfo.token}` },
});
dispatch({ type: DEVOLUCION_SUMMARY_SUCCESS, payload: data });
} catch (error) {
dispatch({
type: DEVOLUCION_CREATE_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
Backend
devolucionRouter.js
import express from 'express';
import expressAsyncHandler from 'express-async-handler';
import Order from '../models/orderModel.js';
import Devolucion from '../models/devolucionModel.js';
import User from '../models/userModel.js';
import Product from '../models/productModel.js';
import {isAdmin, isAuth, isSellerOrAdmin} from '../utils.js';
const devolucionRouter = express.Router();
devolucionRouter.get(
'/mine',
isAuth,
expressAsyncHandler(async (req, res) => {
const devolucion= await Devolucion.find({ user: req.user._id });
res.send(devolucion);
})
);
devolucionRouter.post(
'/',
isAuth,
expressAsyncHandler(async (req, res) => {
if (req.body.devolucionItems.length === 0) {
res.status(400).send({ message: 'No Return' });
} else {
const devolucion = new Devolucion({
seller: req.body.orderItems[0].seller,
orderItems: req.body.orderItems,
devolucionItems: req.body.devolucionItems,
shippingAddress: req.body.shippingAddress,
paymentMethod: req.body.paymentMethod,
itemsPrice: req.body.itemsPrice,
itemsProfit: req.body.itemsProfit,
shippingPrice: req.body.shippingPrice,
taxPrice: req.body.taxPrice,
totalPrice: req.body.totalPrice,
totalProfit: req.body.totalProfit,
user: req.user._id,
});
const createdDevolucion = await devolucion.save();
res
.status(201)
.send({ message: 'New Return Created', devolucion: createdDevolucion });
}
})
);
devolucionRouter.get(
'/:id',
isAuth,
expressAsyncHandler(async (req, res) => {
const devolucion = await Devolucion.findById(req.params.id);
if (devolucion) {
res.send(devolucion);
} else {
res.status(404).send({ message: 'Return Not Found' });
}
})
);
export default devolucionRouter;
server.js
import express from 'express';
import mongoose from 'mongoose';
import dotenv from 'dotenv';
import path from 'path';
import userRouter from './routers/userRouter.js';
import productRouter from './routers/productRouter.js';
import orderRouter from './routers/orderRouter.js';
import devolucionRouter from './routers/devolucionRouter.js';
import uploadRouter from './routers/uploadRouter.js';
dotenv.config();
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
mongoose.connect(process.env.MONGODB_URL || 'mongodb://localhost/AM', {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
});
app.use('/api/uploads', uploadRouter);
app.use('/api/users', userRouter);
app.use('/api/products', productRouter);
app.use('/api/orders', orderRouter);
app.use('/api/devolucions', devolucionRouter);
app.get('/api/config/paypal', (req, res) => {
res.send(process.env.PAYPAL_CLIENT_ID || 'sb');
});
const __dirname = path.resolve();
app.use('/uploads', express.static(path.join(__dirname, '/uploads')));
app.get('/', (req, res) => {
res.send('Server is ready');
});
app.use((err, req, res, next) => {
res.status(500).send({ message: err.message });
});
const port = process.env.PORT || 5000;
app.listen(port, () => {
console.log(`Serve at http://localhost:${port}`);
});
OrderHistoryScreen.js (Frontend)
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { listOrderMine, detailsOrder } from '../actions/orderActions';
import LoadingBox from '../components/LoadingBox';
import MessageBox from '../components/MessageBox';
import {createDevolucion } from '../actions/devolucionActions';
import { DEVOLUCION_CREATE_RESET } from '../constants/devolucionConstants';
export default function OrderHistoryScreen(props) {
const orderId = props.match.params.id;
const order = useSelector((state) => state.order);
const devolucionCreate = useSelector((state) => state.devolucionCreate);
const {success, devolucion } = devolucionCreate;
const orderMineList = useSelector((state) => state.orderMineList);
const { loading, error, orders } = orderMineList;
const dispatch = useDispatch();
useEffect(() => {
dispatch(listOrderMine());
dispatch(detailsOrder(orderId));
}, [dispatch, orderId]);
const placeDevolucionHandler = () => {
dispatch(createDevolucion({ ...order, devolucionItems: order.orderItems }));
};
useEffect(() => {
if (success) {
props.history.push(`/devolucion/${devolucion._id}`);
dispatch({ type: DEVOLUCION_CREATE_RESET });
}
}, [dispatch, devolucion, props.history, success]);
return (
<div>
<h1>Order History</h1>
{loading ? (
<LoadingBox></LoadingBox>
) : error ? (
<MessageBox variant="danger">{error}</MessageBox>
) : (
<table className="table">
<thead>
<tr>
<th>ACTIONS</th>
</tr>
</thead>
<tbody>
{orders.map((order) => (
<tr key={order._id}>
<td>
<button
type="button"
onClick={placeDevolucionHandler}
className="small"
// disabled={cart.cartItems.length === 0}
>
Return Order
</button>
</td>
</tr>
))}
</tbody>
</table>
)}
</div>
);
}
devolucionReducer.js (Frontend)
import {
DEVOLUCION_CREATE_FAIL,
DEVOLUCION_CREATE_REQUEST,
DEVOLUCION_CREATE_RESET,
DEVOLUCION_CREATE_SUCCESS,
DEVOLUCION_DETAILS_FAIL,
DEVOLUCION_DETAILS_REQUEST,
DEVOLUCION_DETAILS_SUCCESS,
DEVOLUCION_DELETE_REQUEST,
DEVOLUCION_DELETE_SUCCESS,
DEVOLUCION_DELETE_FAIL,
DEVOLUCION_DELETE_RESET,
DEVOLUCION_SUMMARY_REQUEST,
DEVOLUCION_SUMMARY_SUCCESS,
DEVOLUCION_SUMMARY_FAIL,
} from '../constants/devolucionConstants';
export const devolucionCreateReducer = (state = {}, action) => {
switch (action.type) {
case DEVOLUCION_CREATE_REQUEST:
return { loading: true };
case DEVOLUCION_CREATE_SUCCESS:
return { loading: false, success: true, devolucion: action.payload };
case DEVOLUCION_CREATE_FAIL:
return { loading: false, error: action.payload };
case DEVOLUCION_CREATE_RESET:
return {};
default:
return state;
}
};
export const devolucionDetailsReducer = (
state = { loading: true },
action
) => {
switch (action.type) {
case DEVOLUCION_DETAILS_REQUEST:
return { loading: true };
case DEVOLUCION_DETAILS_SUCCESS:
return { loading: false, devolucion: action.payload };
case DEVOLUCION_DETAILS_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
export const devolucionDeleteReducer = (state = {}, action) => {
switch (action.type) {
case DEVOLUCION_DELETE_REQUEST:
return { loading: true };
case DEVOLUCION_DELETE_SUCCESS:
return { loading: false, success: true };
case DEVOLUCION_DELETE_FAIL:
return { loading: false, error: action.payload };
case DEVOLUCION_DELETE_RESET:
return {};
default:
return state;
}
};
export const devolucionSummaryReducer = (
state = { loading: true, summary: {} },
action
) => {
switch (action.type) {
case DEVOLUCION_SUMMARY_REQUEST:
return { loading: true };
case DEVOLUCION_SUMMARY_SUCCESS:
return { loading: false, summary: action.payload };
case DEVOLUCION_SUMMARY_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
store.js (frontend)
import { createStore, compose, applyMiddleware, combineReducers } from 'redux';
import thunk from 'redux-thunk';
import { cartReducer } from './reducers/cartReducers';
import {
devolucionCreateReducer,
devolucionDeleteReducer,
devolucionShipReducer,
devolucionDeliverReducer,
devolucionDetailsReducer,
devolucionListReducer,
devolucionMineListReducer,
devolucionPayReducer,
devolucionSummaryReducer,
} from './reducers/devolucionReducers';
import {
orderCreateReducer,
orderDeleteReducer,
orderShipReducer,
orderDeliverReducer,
orderDetailsReducer,
orderListReducer,
orderMineListReducer,
orderPayReducer,
orderSummaryReducer,
orderReducer,
} from './reducers/orderReducers';
import {
productCategoryListReducer,
productCreateReducer,
productDeleteReducer,
productDetailsReducer,
productListReducer,
productReviewCreateReducer,
productUpdateReducer,
} from './reducers/productReducers';
const initialState = {
userSignin: {
userInfo: localStorage.getItem('userInfo')
? JSON.parse(localStorage.getItem('userInfo'))
: null,
},
cart: {
cartItems: localStorage.getItem('cartItems')
? JSON.parse(localStorage.getItem('cartItems'))
: [],
shippingAddress: localStorage.getItem('shippingAddress')
? JSON.parse(localStorage.getItem('shippingAddress'))
: {},
paymentMethod: 'PayPal',
},
};
const reducer = combineReducers({
devolucionCreate: devolucionCreateReducer,
devolucionDetails: devolucionDetailsReducer,
devolucionDelete: devolucionDeleteReducer,
devolucionSummary: devolucionSummaryReducer,
productList: productListReducer,
productDetails: productDetailsReducer,
cart: cartReducer,
order: orderReducer,
userSignin: userSigninReducer,
userRegister: userRegisterReducer,
orderCreate: orderCreateReducer,
orderDetails: orderDetailsReducer,
orderPay: orderPayReducer,
orderMineList: orderMineListReducer,
orderSummary: orderSummaryReducer,
userDetails: userDetailsReducer,
userUpdateProfile: userUpdateProfileReducer,
userUpdate: userUpdateReducer,
productCreate: productCreateReducer,
productUpdate: productUpdateReducer,
productDelete: productDeleteReducer,
orderList: orderListReducer,
orderDelete: orderDeleteReducer,
orderShip: orderShipReducer,
orderDeliver: orderDeliverReducer,
productReviewCreate: productReviewCreateReducer,
userList: userListReducer,
userDelete: userDeleteReducer,
productCategoryList: productCategoryListReducer,
});
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancer(applyMiddleware(thunk))
);
export default store;
I'm getting this error when running a test, however I have wrapped the component I am testing in a <BrowserRouter> component:
● Axios › gets a response
Invariant failed: You should not use <Link> outside a <Router>
91 |
92 | if (RootComponent) {
> 93 | const component = mountWithCustomWrappers(<Wrapper store={store}><RootComponent
{...props} />, rootWrappers);
nock.integration.test
import React from 'react';
import axios from 'axios';
const core = require('tidee-life-core');
import httpAdapter from 'axios/lib/adapters/http';
import doFetch from '../../../helpers/doFetch.js';
import ComponentBuilder from "../component-builder";
import LoginPage from "../../../scenes/login/login-page";
const host = 'http://example.com';
process.env.API_URL = host;
axios.defaults.host = host;
axios.defaults.adapter = httpAdapter;
const makeRequest = () => {
return doFetch({
url: core.urls.auth.login(),
queryParams: { foo: 'bar' },
})
.then(res => res.data)
.catch(error => console.log(error));
};
describe('Axios', () => {
let component;
let componentBuilder;
beforeEach(() => {
componentBuilder = new ComponentBuilder();
});
test('gets a response', async () => {
componentBuilder.includeInterceptor('login');
component = await componentBuilder.build({
RootComponent: LoginPage,
selector: 'LoginForm',
});
return makeRequest()
.then(response => {
expect(typeof response).toEqual('object');
expect(response.data.token).toEqual('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhNDY3MGE3YWI3ZWM0ZjQ2MzM4ODdkMzJkNzRkNTY5OSIsImlhdCI6MTU1MjA5MDI1NX0.vsKLXJEqSUZK-Y6IU9PumfZdW7t1SLM28jzJL89lcrA');
});
});
});
login-page.jsx:
import React, { Component } from 'react';
import PropTypes from "prop-types";
import { withRouter } from 'react-router-dom';
import { injectIntl, intlShape } from 'react-intl';
import queryString from 'query-string';
import { Link } from 'tidee-life-ui';
import LoginForm from './login-form.jsx';
import { doLogin } from '../../api/auth/auth-api';
import Auth from '../../modules/Auth';
import messages from '../../messages';
const { urls } = require('tidee-life-core');
class LoginPage extends Component {
constructor(props) {
super(props);
const { intl } = props;
if (Auth.isUserAuthenticated()) {
props.history.replace({ pathname: urls.pages.pathBoxes() });
}
this.messages = {
'account.activation.error.expired': intl.formatMessage(messages['account.activation.error.expired']),
'account.activation.required': intl.formatMessage(messages['account.activation.required']),
'common.click': intl.formatMessage(messages['common.click']),
'common.here': intl.formatMessage(messages['common.here']),
'error.500.msg': intl.formatMessage(messages['error.500.msg']),
'forgot.success': intl.formatMessage(messages['forgot.success']),
'login.account.needs.activating.partial': intl.formatMessage(messages['login.account.needs.activating.partial']),
'login.error.account.credentials': intl.formatMessage(messages['login.error.account.credentials']),
'login.validation.email': intl.formatMessage(messages['login.validation.email']),
'login.validation.password': intl.formatMessage(messages['login.validation.password']),
'signup.account.created': intl.formatMessage(messages['signup.account.created'])
};
let alertMessage;
let alertMessageType;
const query = queryString.parse(props.location.search);
if ('signup-success' in query) {
alertMessage = this.messages['signup.account.created'];
alertMessageType = 'success';
} else if ('forgot-success' in query) {
alertMessage = this.messages['forgot.success'];
alertMessageType = 'success';
}
this.state = {
alert: {
type: alertMessageType ? alertMessageType : '',
msg: alertMessage ? alertMessage : '',
},
user: {
email: '',
password: ''
}
};
this.changeUser = this.changeUser.bind(this);
this.clearAlert = this.clearAlert.bind(this);
this.processForm = this.processForm.bind(this);
}
clearAlert() {
this.setState({ alert: {
type: '',
msg: '',
}});
}
processForm(e) {
e.preventDefault();
return doLogin({
email: this.state.user.email,
password: this.state.user.password,
}).then((response) => {
Auth.authenticateUser(response.data.token);
this.props.history.replace({ pathname: urls.pages.pathBoxes() });
}).catch((error) => {
const msg = error.message && this.messages[error.message] ? [this.messages[error.message]] : [this.messages['error.500.msg']];
if (error.message === 'account.activation.error.expired' || error.message === 'account.activation.required') {
const to = urls.pages.pathResendLink(error.data.confirmHash);
msg.push(` ${this.messages['common.click']} `);
msg.push(<Link underline color="inherit" key="email" to={to}>{this.messages['common.here']}</Link>);
msg.push(` ${this.messages['login.account.needs.activating.partial']}`);
}
this.setState({
alert: {
type: 'error',
msg,
}
});
});
}
changeUser(event) {
const { name, value } = event.target;
this.setState((currentState) => ({
user: {
...currentState.user,
[name]: value,
}
}));
}
render() {
return (
<LoginForm
data-test="login-form"
alert={this.state.alert}
onSubmit={this.processForm}
onChange={this.changeUser}
user={this.state.user}
/>
);
}
}
LoginPage.propTypes = {
history: PropTypes.object,
intl: intlShape.isRequired,
location: PropTypes.object.isRequired,
};
export default injectIntl(withRouter(LoginPage));
component-builder.js
import React from "react";
import nock from 'nock';
import cloneDeep from 'lodash.clonedeep';
import { mountWithCustomWrappers } from 'enzyme-custom-wrappers';
import { waitForStoreState } from './wait/wait-for-store-state';
import Wrapper from './wrapper.jsx';
import waitForComponentPredicate from './wait-for-component-predicate/wait-for-component-predicate';
import waitForComponentSelector from './wait-for-component-selector/wait-for-component-selector';
import { startAllNockServiceIntercepts } from './nock/nock-manager';
import nockServices from './nock/services';
import store from "../../store/store";
import wrappers from './wrappers';
const rootWrappers = component => wrappers(component);
class ComponentBuilder {
constructor() {
this.nockInterceptors = [];
this.storePreparers = [];
}
includeInterceptor( interceptorName, nockConfigOverride = null ) {
// Maybe need to do a clone deep here if things start breaking!
const clonedNockService = cloneDeep(nockServices[interceptorName]().nockConfig);
const nockService = {
[interceptorName]: {
...clonedNockService,
...(nockConfigOverride || {}),
}
};
this.nockInterceptors.push(nockService);
}
prepareStore( storePreparer ) {
this.storePreparers.push(storePreparer);
}
async waitForStoreToUpdate() {
const promises = this.storePreparers
.map(service => waitForStoreState(service.redux.storeStateToWaitFor, store));
await Promise.all(promises);
}
async runStorePreparers() {
nock.cleanAll();
const interceptors = [];
this.storePreparers.forEach((service) => {
const interceptorName = service.http.interceptor;
const clonedNockService = service.http.interceptor && cloneDeep(nockServices[interceptorName]().nockConfig);
interceptors.push({
[interceptorName]: {
...clonedNockService,
}
});
});
startAllNockServiceIntercepts(interceptors);
this.storePreparers.forEach(service => service.redux.actionToDispatch && store.dispatch(service.redux.actionToDispatch()));
return await this.waitForStoreToUpdate();
}
/**
* Build a component to be tested.
* #param RootComponent
* #param selector {string} - A selector to wait for. CSS selector or name of component.
* #param props {object}
* #param store {object}
* #param predicate {function} - A function that returns true if a condition is met.
* #param predicateMaxTime {number}
* #param predicateInterval {number}
* #returns {Promise<*>}
*/
async build({
RootComponent = null,
selector = '',
props = {},
predicate = null,
predicateMaxTime = 2000,
predicateInterval = 10,
} = {}) {
try {
await this.runStorePreparers();
startAllNockServiceIntercepts(this.nockInterceptors);
if (RootComponent) {
const component = mountWithCustomWrappers(<Wrapper store={store}><RootComponent {...props} /></Wrapper>, rootWrappers);
if (selector) {
await waitForComponentSelector({ selector, rootComponent: component, store });
}
if (predicate) {
await waitForComponentPredicate({
predicate,
rootComponent: component,
store,
maxTime: predicateMaxTime,
interval: predicateInterval,
});
}
return component;
}
} catch(err) {
throw err;
}
}
}
export default ComponentBuilder;
wrapper.jsx
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { BrowserRouter } from "react-router-dom";
import { Provider } from 'react-redux';
import ThemeProvider from "../../theme/Theme.jsx";
import LocaleProviderWrapper from "./locale-provider-wrapper.jsx";
const propTypes = {
children: PropTypes.element.isRequired,
store: PropTypes.object.isRequired,
};
class Wrapper extends Component {
getStore() {
return this.props.store;
}
render() {
return (
<Provider store={this.props.store}>
<LocaleProviderWrapper>
<ThemeProvider>
<BrowserRouter>{this.props.children}</BrowserRouter>
</ThemeProvider>
</LocaleProviderWrapper>
</Provider>
);
}
}
Wrapper.propTypes = propTypes;
export default Wrapper;
Exactly as message says, you cannot use Link which doesn't have any parent of type Router. In you processForm function you are building a messaage with Link component which is worng.
if (error.message === 'account.activation.error.expired' || error.message === 'account.activation.required') {
const to = urls.pages.pathResendLink(error.data.confirmHash);
msg.push(` ${this.messages['common.click']} `);
msg.push(<Link underline color="inherit" key="email" to={to}>{this.messages['common.here']}</Link>);
msg.push(` ${this.messages['login.account.needs.activating.partial']}`);
}
You should use a tag to build dynamic link. May be something like:
msg.push(`${this.messages['common.here']}`);
I'm having trouble to get the data of array in JSON.
I send the json file by using node.js and the code is down below.
const express = require("express");
const router = express.Router(); // 라우터 분리
router.get("/api/hello", (req, res) => {
// app 대신 router에 연결
res.json({ express: "hello~" });
});
router.get("/api/beef", (req, res) => {
res.json();
});
module.exports = router; // 모듈로 만드는 부분
And the data which I send with json is like this.
{
"test": "Beef data get Completed",
"name": "beef",
"data": [
{ "productName": "양지", "price": 20000 },
{ "productName": "갈비", "price": 30000 },
{ "productName": "등심", "price": 15000 }
]
}
]
And then I get the data by using fetch, and save it in the state.
I get the right data which I want in the fetch function but when I try to get the data out of the fetch function I keep getting the error like cannot get the property blahblahblah...
And this is the code I did in the client.
import React, { Component } from "react";
const itemList = ["돼지고기", "소고기", "닭&오리", "Sale", "연락처"];
class Contents extends Component {
state = {
parsedJson : "",
};
componentDidMount() {
this._callAPI()
.then(parsedJson=>{
console.log("In the fetch : ",parsedJson[0].data[0])
this.setState({
parsedJson
})
})
.catch(error => {
console.log(error);
this.setState({
...this.state,
isError: true
});
});
}
_callAPI = async () => {
const response = await fetch(`api/${this.props.curPage}`);
const data = response.json();
if (response.status !== 200) throw Error(data.message);
return data;
};
render() {
const stateData = this.state;
console.log("In the render : ",stateData.parsedJson[0].data[0]) <- !!!!!!!!!!!! where error occur
return (
<>
<RenderByItemList
curPage={this.props.curPage}
data={stateData.productData}
/>
</>
);
}
}
On the first render of your component, parsedJson is string, and the stateData.parsedJson[0] returns an empty string, and there is no data property on an empty string, so you get the error.
For solving this problem you have to write an if inside your render method:
import React, { Component } from "react";
const itemList = ["돼지고기", "소고기", "닭&오리", "Sale", "연락처"];
class Contents extends Component {
state = {
parsedJson: [],
error: null,
};
componentDidMount() {
this._callAPI()
.then(res => {
this.setState({
parsedJson: res[0].data, // here we set state the data array
})
})
.catch(error => {
console.log(error);
this.setState({
...this.state,
error // here we set state occurred error
});
});
}
_callAPI = async () => {
const response = await fetch(`api/${this.props.curPage}`);
const data = response.json();
if (response.status !== 200) throw Error(data.message);
return data;
};
render() {
const { parsedJson, error } = this.state;
if (parsedJson && parsedJson.length) {
return <RenderByItemList
curPage={this.props.curPage}
data={parsedJson}
/>
} else {
return !!error || 'loading';
}
}
}
And also it's better to handle loading on your fetch action:
import React, { Component } from "react";
const itemList = ["돼지고기", "소고기", "닭&오리", "Sale", "연락처"];
class Contents extends Component {
state = {
parsedJson: [],
error: null,
loading: false
};
componentDidMount() {
this.setState({
loading: true,
}, () => {
this._callAPI()
.then(res => {
this.setState({
parsedJson: res[0].data, // here we set state the data array
loading: false,
error: null,
})
})
.catch(error => {
console.log(error);
this.setState({
loading: false,
error // here we set state occurred error
});
});
})
}
_callAPI = async () => {
const response = await fetch(`api/${this.props.curPage}`);
const data = response.json();
if (response.status !== 200) throw Error(data.message);
return data;
};
render() {
const { parsedJson, error, loading } = this.state;
if (loading) {
return 'loading' // or write a Loading component and render it everywhere
} else if (error) {
return error // or write an Error component
} else {
return (
<RenderByItemList
curPage={this.props.curPage}
data={parsedJson}
/>
);
}
}
}
I have written a server and wired up React with Redux, also made use of container-componenent-seperation. The first action fetchSnus() is successfully dispatched, the selectors also seem to work, but for some reason all the actions that I call after the first rendering, such as fetchSnu() (that only fetches ONE single snu-object) e.g. is not accessible for me in the store, meaning: after mapping state and dispatch to props not accessible for me under this.props. I really don't understand why!
You can see the whole project here: https://github.com/julibi/shonagon
This is my container ReadSingleSnuContainer.js:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchSnus, fetchSnu, setToRead, createSnu, getSnusMatchingKeyword } from '../../actions/index';
import { unreadSnus, randomFirstSnu } from '../../selectors/index';
import ReadSingleSnu from './ReadSingleSnu';
class ReadSingleSnuContainer extends Component {
componentWillMount() {
this.props.fetchSnus();
}
render() {
return (
<ReadSingleSnu { ...this.props } />
);
}
}
const mapStateToProps = (state) => {
return {
snus: state.snus,
randomFirstSnu: randomFirstSnu(state),
candidate: state.candidate
};
}
const mapDispatchToProps = (dispatch) => {
return {
fetchSnus: () => dispatch(fetchSnus()),
getSnusMatchingKeyword,
fetchSnu,
setToRead,
createSnu
}
};
export default connect(mapStateToProps, mapDispatchToProps)(ReadSingleSnuContainer);
This is my component ReadSingleSnu.js:
import React, { Component } from 'react';
import style from './ReadSingleSnu.css'
import Typist from 'react-typist';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
export default class ReadSingleSnu extends Component {
constructor(props) {
super(props);
this.state = { showTitles: false };
}
renderRandomFirstSnu() {
const { randomFirstSnu } = this.props;
const { showTitles } = this.state;
// preselect a keyword
// dispatch the action that searches for other keywords (action I)
if(randomFirstSnu) {
return (
<div>
<h3><Typist cursor={ { show: false } }>{ randomFirstSnu.title }</Typist></h3>
<ReactCSSTransitionGroup
transitionName="snu"
transitionAppear={ true }
transitionAppearTimeout={ 1000 }
transitionEnter={ false }
transitionLeave={ false }
>
<p>{ randomFirstSnu.text }</p>
</ReactCSSTransitionGroup>
{ !showTitles ? (
<div>
<button>Some other</button>
<button onClick={ () => this.handleDoneReading(randomFirstSnu) }>Done reading, next</button>
</div>
) : (
<ReactCSSTransitionGroup
transitionName="keywords"
transitionAppear={ true }
transitionAppearTimeout={ 1000 }
transitionEnter={ false }
transitionLeave={ false }
>
<ul>{ randomFirstSnu.keywords.map((keyword, idx) =>
<li key={ idx }>
<button onClick={ () => this.fetchNextSnu(randomFirstSnu) }>
{ keyword }
</button>
</li>) }
</ul>
</ReactCSSTransitionGroup>
)
}
</div>
);
}
return <div>Loading ...</div>
}
handleDoneReading(snu) {
const { setToRead, getSnusMatchingKeyword } = this.props;
const id = snu._id;
if (snu.keywords.length > 0 && setToRead) {
// setToRead(id, snu);
this.setState({ showTitles: true });
const randomIndex = Math.floor(Math.random() * snu.keywords.length);
const randomKeyword = snu.keywords[randomIndex];
console.log('This is the randomKeyword :', randomKeyword);
getSnusMatchingKeyword(randomKeyword);
} else {
console.log('Here will soon be the select random next snu action :)');
}
}
render() {
console.log('ReadSingleSnu, this.props: ', this.props);
return (
<div className={style.App}>
<div>{ this.renderRandomFirstSnu() }</div>
</div>
);
}
}
This is my actions file:
import axios from 'axios';
export const FETCH_SNUS = 'FETCH_SNUS';
export const FETCH_SNU = 'FETCH_SNU';
export const SET_TO_READ = 'SET_TO_READ';
export const CREATE_SNU = 'CREATE_SNU';
export function getSnusMatchingKeyword(keyword) {
const request = axios.get(`/snus/keyword/${keyword}`);
return {
type: GET_SNUS_MATCHING_KEYWORD,
payload: request
};
}
export function fetchSnus() {
const request = axios.get('/snus');
return {
type: FETCH_SNUS,
payload: request
};
}
export function fetchSnu(id) {
const request = axios.get(`/snus/${id}`);
return {
type: FETCH_SNU,
payload: request
};
}
export function setToRead(id, snu) {
const request = axios.patch(`/snus/${id}`, { title: snu.title, text: snu.text, keywords: snu.keywords, read: true });
return {
type: SET_TO_READ,
payload: request
}
}
export function createSnu(object) {
const request = axios.post('/snus', object);
return {
type: CREATE_SNU,
payload: request
};
}
A Reducer:
import { FETCH_SNUS, FETCH_SNU, SET_TO_READ, CREATE_SNU } from '../actions/index';
export default function(state = [], action) {
switch(action.type) {
case FETCH_SNUS:
return [ ...state, ...action.payload.data ];
case FETCH_SNU:
return [ ...state, ...action.payload.data ];
case SET_TO_READ:
return [ ...state, ...action.payload.data ];
case CREATE_SNU:
return [...state, ...action.payload.data ];
default:
return state;
}
}
I tested all endpoints via Postman and they work. So that should not be the problem… Please help! I cannot find a solution to this problem.