React ReferenceError: document is not defined - javascript

I'm trying to render the following component on the server side as part of a universal/isomorphic app:
import React, { PropTypes, Component } from 'react';
import { connect } from 'react-redux';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { GridLoader } from 'halogen';
import PostListItem from '../../components/PostListItem/PostListItem';
import { primary as color } from '../../colors';
import { changeSelectedPost, deletePostRequest } from '../../redux/modules/post';
export default connect()(class PostListView extends Component {
static propTypes = {
posts: ImmutablePropTypes.listOf(ImmutablePropTypes.contains({
name: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
content: PropTypes.string.isRequired,
slug: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
})).isRequired,
loading: PropTypes.boolean.isRequired,
dispatch: PropTypes.func.isRequired,
}
handleClick(post) {
this.props.dispatch(changeSelectedPost(post));
}
handleDelete(post) {
if (confirm('Do you want to delete this post')) { // eslint-disable-line
this.props.dispatch(deletePostRequest(post));
}
}
render() {
if (typeof window !== 'undefined' && this.props.loading) {
return (
<div className="container">
<GridLoader color={color} />
</div>
);
}
return (
<div className="listView">
{
this.props.posts.toSeq().map((post, i, arr) => (
<PostListItem
post={post}
key={i}
onClick={this.handleClick.bind(this, post)}
onDelete={this.handleDelete.bind(this, post)}
/>
))
}
</div>
);
}
});
but I receive the error:
module.exports = document.createElement('div').style;
^
ReferenceError: document is not defined
Adding the if block seems to have tripped up the app (the app rendered perfectly before, both server and client side). I'm most likely missing something quite obvious, knowing my track record :P. Any suggestions? :)
UPDATE: File that handles server-side rendering and the rest of the app on the server side:
// server/server.js
'use-strict';
import path from 'path';
import bodyParser from 'body-parser';
import Express from 'express';
import React from 'react';
import webpack from 'webpack';
import { fromJS } from 'immutable';
import webpackDevMiddleware from 'webpack-dev-middleware';
import webpackHotMiddleware from 'webpack-hot-middleware';
import { match, RouterContext } from 'react-router';
import { Model } from 'objection';
import { Provider } from 'react-redux';
import { renderToString } from 'react-dom/server';
import config from '../webpack.config.dev';
import routes from '../shared/routes';
import configureStore from '../shared/redux/configureStore';
import assets from './assets';
import db from './db';
import posts from './routes/post.routes';
import Post from './models/post';
import serverConfig from './serverConfig';
// Initialize the Express App
const app = new Express();
Model.knex(db);
if (process.env.NODE_ENV !== 'production') {
const compiler = webpack(config);
app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath }));
app.use(webpackHotMiddleware(compiler));
}
// Apply body Parser and server public assets and routes
app.use(bodyParser.json({ limit: '20mb' }));
app.use(bodyParser.urlencoded({ limit: '20mb', extended: false }));
if (process.env.NODE_ENV !== 'production') {
app.use(Express.static(path.resolve(__dirname, '../static')));
}
function getFilename() {
if (process.env.NODE_ENV !== 'production') {
return '"/dist/bundle.js"';
}
return `"/dist/${assets}"`;
}
app.use('/api', posts);
// Render Initial HTML
const renderFullPage = (html, initState, jsFile) => {
return `
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/styles/normalize.css">
<link rel="stylesheet" href="/styles/skeleton.css">
<title>CeresShop</title>
</head>
<div id="root">${html}</div>
<script>
window.__INITIAL_STATE__ = ${JSON.stringify(initState)};
</script>
<script src=${jsFile}></script>
</body>
</html>
`;
};
// Server Side Rendering based on routes matched by React-router.
app.use((req, res) => {
match({ routes, location: req.url }, (err, redirectLocation, renderProps) => {
if (err) {
return res.status(500).end('Internal server error');
}
if (!renderProps) {
return res.status(404).end('Not found!');
}
const initialState = fromJS({
postReducer: {
posts: [],
post: {},
loading: false,
},
route: {
locationBeforeTransitions: null,
},
});
async function loadData() {
if (req.url.includes('post')) {
try {
const newSlug = req.url.substring(5).split('-');
const newId = newSlug[newSlug.length - 1];
const newPost = await Post.query().where('id', newId);
const toBeProcessed = JSON.stringify(newPost[0]);
return initialState.setIn(['postReducer', 'post'], fromJS(JSON.parse(toBeProcessed)));
} catch (error) {
console.log(error);
return initialState;
}
}
try {
const newPosts = await Post.query();
newPosts.sort((a, b) => b.dateadded - a.dateadded);
const toBeProcessed = JSON.stringify(newPosts);
return initialState.setIn(['postReducer', 'posts'], fromJS(JSON.parse(toBeProcessed)));
} catch (error) {
console.log(error);
return initialState;
}
}
loadData().then((currentState) => {
const store = configureStore(currentState);
const createElement = (Component, props) => (
<Component
{...props}
radiumConfig={{ userAgent: req.headers['user-agent'] }}
/>
);
const initialView = renderToString(
<Provider store={store}>
<RouterContext {...renderProps} createElement={createElement} />
</Provider>
);
const finalState = store.getState().toJS();
res.status(200).end(renderFullPage(initialView, finalState, getFilename()));
}).catch(err1 => {
console.log(err1);
res.end(renderFullPage(`Error: ${err1}`, {}, getFilename()));
});
});
});
// start app
app.listen(serverConfig.port, (error) => {
if (!error) {
console.log(`DAT SERVER is running on port: ${serverConfig.port}!`); // eslint-disable-line
}
});
export default app;

If you have a component that is being rendered server-side that requires another component which will only will be rendered in the client, it will still attempt to export that module on the server. You can either check if document exists, or require the module inline.
module.exports = typeof document !== 'undefined' ? document.createElement('div').style : null
// or
componentDidMount() {
// this is only called in the client
require('./clientComponent')
}

Related

axios GET request not working in MERN application [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
So, I am attempting a simple, single-page application on the MERN stack that takes notes - a note title and a note content and displays them on the same root route; the data is supposed to also be saved on the backend for later retrieval. At this time, there is no authentication. My backend seems to be working perfectly, but when I connect the front end React application to the backend MongoDB database, my GET request (using axios instance) fails.
My backend renders on localhost:5000, no problem.
But on localhost:3000, I'm seeing this error in App.jsx:
Error: Request failed with status code 404
at createError (createError.js:16)
at settle (settle.js:17)
at XMLHttpRequest.handleLoad (xhr.js:62)
Here's what I have going on:
BACKEND
// server.js
import express from 'express';
import cors from 'cors';
import notes from './api/notes.route.js';
const app = express();
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use('/api/v1/notes', notes);
app.use('*', (req, res) => res.status(404).json({ error: 'not found' }));
export default app;
// notes.route.js
import express from 'express';
import NotesController from './notes.controller.js';
const router = express.Router();
router
.route('/')
.get(NotesController.apiGetNotes)
.post(NotesController.apiPostNote)
.put(NotesController.apiUpdateNote)
.delete(NotesController.apiDeleteNote);
export default router;
// notes.controller.js
import NotesDAO from '../dao/notesDAO.js';
class NotesController {
static apiGetNotes = async (req, res, next) => {
const notesPerPage = req.query.notesPerPage
? parseInt(req.query.notesPerPage)
: 20;
const page = req.query.page ? parseInt(req.query.page) : 0;
const { notesList, totalNumNotes } = await NotesDAO.getNotes({
page,
notesPerPage
});
let response = {
notesList,
page,
notesPerPage,
totalNumNotes
};
res.json(response);
};
export default NotesController;
// notesDAO.js
import mongodb from 'mongodb';
const ObjectID = mongodb.ObjectId;
let notes;
class NotesDAO {
// call this on db connection:
static injectDB = async conn => {
if (notes) return;
try {
notes = await conn.db(process.env.NOTESDB_NS).collection('notes');
} catch (e) {
console.error(`unable to establish collection handle in notesDAO: ${e}`);
}
};
static getNotes = async ({ page = 0, notesPerPage = 20 } = {}) => {
let query;
let cursor;
try {
cursor = await notes.find(query);
} catch (e) {
console.error(`unable to issue find command, ${e}`);
return { notesList: [], totalNumNotes: 0 };
}
const displayCursor = cursor.limit(notesPerPage).skip(notesPerPage * page);
try {
const notesList = await displayCursor.toArray();
const totalNumNotes = await notes.countDocuments(query);
return { notesList, totalNumNotes };
} catch (e) {
console.error(
`unable to convert cursor to array or problem counting documents, ${e}`
);
return { notesList: [], totalNumNotes: 0 };
}
};
}
export default NotesDAO;
FRONTEND
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Route } from 'react-router-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<Route path="/" component={App} />
</BrowserRouter>
</React.StrictMode>,
document.getElementById('root')
);
// App.jsx
import { useState, useEffect } from 'react';
import NoteDataService from './services/note.js';
import Header from './Header';
import Footer from './Footer';
import CreateArea from './CreateArea';
import Notes from './Notes';
const App = () => {
const [notes, setNotes] = useState([]);
useEffect(() => {
retrieveNotes();
}, []);
const retrieveNotes = async () => {
await NoteDataService.getAll()
.then(response => {
console.log(response.data);
setNotes(response.data.notes);
})
.catch(e => console.log(e));
};
return (
<>
<Header />
<CreateArea clicked={addNote} />
<Notes notes={notes} clicked={deleteNote} />
<Footer />
</>
);
};
export default App;
// http-common.js
import axios from 'axios';
export default axios.create({
baseURL: 'http://localhost:5000/api/vi/notes',
headers: {
'Content-type': 'application/json'
}
});
// note.js
import http from '../http-common.js';
class NoteDataService {
getAll() {
return http.get('/');
}
}
export default new NoteDataService();
You have a typo. The backend route is /api/v1/notes, but the frontend is sending requests to /api/vi/notes

SSR app but React Router Navigation on the client side doesn't fetch data and breaks the page

I've created a SSR React app that loads data on the server and sends it to the client as html. The problem kicks in after the initial server request has been served and i try to switch to a different navigation link. The url changes to the correct path but the page itself breaks with a TypeError: Cannot read property 'length' or 'map' of undefined. I believe the fetching somehow is not working on the client side, because if i turn off JavaScript from the browser everything works just fine.
The App has four routes, Home, Movies (needs to fetch data), TvShows (needs to fetch data) and PageNotFound. Again, the problem occurs when for example I open the Home page and try to switch to Movies. However, if i open Movies or TvShows first everything loads correctly because of the initial request being served by the server. Here is my file structure:
And here's the content of some of my files:
index.js (Server.js)
import "#babel/polyfill";
import express from "express";
import { applyMiddleware, createStore } from "redux";
import Routes from "./client/Routes";
import { matchRoutes } from "react-router-config";
import renderer from "./helpers/renderer.js";
import thunk from "redux-thunk";
import reducers from "./reducers";
const compression = require("compression");
const app = express();
app.use(compression());
app.use(express.static("public")); //treats the public(client side) directory as public, available to the outside world
// This is fired every time the server side receives a request
app.get("*", (req, res) => {
// Create a new Redux store instance
const store = createStore(reducers, {}, applyMiddleware(thunk));
const promises = matchRoutes(Routes, req.path)
.map(({ route }) => {
return route.loadData ? route.loadData(store) : null;
})
.map((promise) => {
if (promise) {
return new Promise((resolve, reject) => {
promise.then(resolve).catch(resolve);
});
}
});
Promise.all(promises).then(() => {
// Send the rendered page back to the client
// Grab the initial state from our Redux store
const context = {};
//const finalState = store.getState();
const content = renderer(req, store, context);
if (context.notFound) {
res.status(404);
}
res.send(content);
});
});
const PORT = process.env.PORT || 3000;
app.listen(PORT);
client.js
import "#babel/polyfill";
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { Provider } from "react-redux";
import { renderRoutes } from "react-router-config";
import Routes from "./Routes";
import reducers from "../reducers";
// Grab the state from a global variable injected into the server-generated HTML
const preloadedState = window.__PRELOADED_STATE__;
// Create Redux store with initial state
const store = createStore(reducers, preloadedState, applyMiddleware(thunk));
ReactDOM.hydrate(
<Provider store={store}>
<BrowserRouter>
<div>{renderRoutes(Routes)}</div>
</BrowserRouter>
</Provider>,
document.querySelector("#root")
);
renderer.js
import React from "react";
import serialize from "serialize-javascript";
import { renderToString } from "react-dom/server";
import { StaticRouter } from "react-router-dom";
import { Provider } from "react-redux";
import Routes from "../client/Routes";
import { renderRoutes } from "react-router-config";
import { Helmet } from "react-helmet";
// Render the component to a string
export default (req, store, context) => {
const html = renderToString(
<Provider store={store}>
<StaticRouter location={req.path} context={context}>
<div>{renderRoutes(Routes)}</div>
</StaticRouter>
</Provider>
);
const helmet = Helmet.renderStatic();
return `
<!doctype html>
<html>
<head>
${helmet.title.toString()}
${helmet.meta.toString()}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap#4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
</head>
<body>
<div id="root">${html}</div>
<script>
// WARNING: See the following for security issues around embedding JSON in HTML:
// https://redux.js.org/recipes/server-rendering/#security-considerations
window.__PRELOADED_STATE__ = ${serialize(store.getState())}
</script>
<script src="/bundle.js"></script>
</body>
</html>
`;
};
Routes.js
import App from "./App";
import HomePage from "./pages/HomePage";
import MovieListPage from "./pages/MovieListPage";
import TvShowsPage from "./pages/TvShowsPage";
import NotFoundPage from "./pages/NotFoundPage";
//using spread operator for the components
//and loadData function(if available)
//because they are imported in object form now
export default [
{
...App, //no path added to App, meaning it will always be displayed on screen
routes: [
{
...HomePage,
path: "/",
exact: true,
},
{
...MovieListPage,
path: "/movies",
exact: true,
},
{
...TvShowsPage,
path: "/tvshows",
exact: true,
},
{
...NotFoundPage, //will be shown if react router can't match any of the defined routes
},
],
},
];
MovieListPage.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { fetchMovies } from "../../actions";
import { Helmet } from "react-helmet";
class MovieListPage extends Component {
// Have state ready for both Movies and TvShows link clicks/direct requests
componentDidMount() {
if (this.props.movies) return;
this.props.fetchMovies();
}
renderData() {
return this.props.movies.results.map((movie) => {
return (
<div
key={movie.id}
className="card text-center m-3"
style={{ width: "15rem" }}
>
<img
className="card-img-top"
alt="..."
src={this.dynamicUrl(movie)}
/>
<div className="card-body">
<h5 className="card-title">{movie.title}</h5>
<p className="card-text font-weight-light">{movie.release_date}</p>
<a href={this.dynamicLink(movie)} className="btn btn-secondary">
TMDB
</a>
</div>
</div>
);
});
}
dynamicUrl(movie) {
let url = "https://image.tmdb.org/t/p/w200/" + movie.poster_path;
return url;
}
dynamicLink(movie) {
let link = "https://www.themoviedb.org/movie/" + movie.id;
return link;
}
head() {
return (
<Helmet>
<title>{`${this.props.movies.results.length} Movies Loaded`}</title>
<meta property="og:title" content="Movies" />
</Helmet>
);
}
render() {
return (
<div className="container">
{this.head()}
<div className="row">{this.renderData()}</div>
</div>
);
}
}
function mapStateToProps(state) {
return { movies: state.movies };
}
function loadData(store) {
return store.dispatch(fetchMovies());
}
//exporting the component and the loadData function (if present)
//in the form of an object(key:value pair)
// to avoid overlap of different loadData function imports in Routes
export default {
loadData,
component: connect(mapStateToProps, { fetchMovies })(MovieListPage),
};
I can't seem to figure out what is it that i'm missing.
I found the issue in renderer.js. I didn't provide the correct path for my client-side bundle.js in the html served by the server. Instead of <script src="/bundle.js"></script> it had to be <script src="/public/bundle.js"></script>.

writing test for service calls in enzyme - react

how can i write the test cases for the below component using jest.
should i mock the import files.
// App.js
import React, {Component} from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Layout from "../Layout";
import Home from "../components/pages/Home";
import Users from "../components/pages/Users";
import UserList from "../components/pages/UserList";
import AppContext from "../components/context/App/index";
import Loader from "../components/Loader";
import Error from "../components/Error";
import config from "../config";
import http from '../lib/http';
class Routes extends Component {
state = {
displayLoader: true,
displayError: false,
errorMessage: null,
data: null
};
// Life cycle methods
componentWillMount() {
this.setState({
displayLoader: true
});
}
componentDidMount() {
this._resolveData();
}
_resolveData = async () => {
try {
const data = await http.get(config.endPoints.dataEndPoint);
// check list are available
if (data.list) {
this.setState({
data: data.list,
displayLoader: false
});
} else {
const error = {
message: data.message,
status: data.status
};
throw error;
}
} catch (error) {
this.setState({
displayLoader: false,
displayError: true,
errorMessage: error.message
});
}
};
render() {
const {
displayLoader,
displayError,
errorMessage,
data
} = this.state;
return displayLoader ? (
<Loader />
) : displayError ? (
<Error message={errorMessage} />
) : Object.keys(data).length > 0 ? (
<Router>
<AppContext.Provider value={data}>
<Layout>
<Switch>
<Route path="/sample" exact component={Home} />
<Route path="/sample/userList/" component={UserList} />
<Route path="/sample/users/" component={Users} />
</Switch>
</Layout>
</AppContext.Provider>
</Router>
) : (
<p>Please contact support team</p>
);
}
}
export default Routes;
//lib/http.js
async function get(url) {
try {
const response = await fetch(url, { method: 'get' });
const body = await response.json();
return body;
}
catch (err) {
throw err;
}
}
export default {get}
//app.test.js
import React from 'react';
import {shallow} from 'enzyme'
import App from '..';
describe('Given the Router component is rendered', () => {
test('it should render the markup', () => {
const tree = shallow(<App />);
expect(tree).toMatchSnapshot();
});
test('should get the data once service resolves', () => {
})
});
any help appreciated :)

How to fix get request and response with whole html

I'm new to react, redux and all the other stuff and I am struggling with some problems. My first problem is that I receive from an axios "get" request as a response my whole side. (I think my webpack makes some trouble) The second problem is that my Axios function will fail with the error "this.props.getBranch is not a function".
I have tried to fix the problems and searched for a while without a solution... So it would be great if you could help me.
my code
Multistep Form
import React, { Component } from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import RiskDetails from "./Step1/RiskDetails";
import { getBranch } from "../../actions/riskActions";
class RiskForm extends Component {
constructor(props) {
super(props);
this.state = {
step: 1,
risikoName: "",
risikoBereich: "",
risikoKlasse: "",
branche: ""
};
this.onChange = this.onChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
}
nextStep() {
let step = this.state.step;
this.setState({ step: step + 1 });
}
prevStep() {
let step = this.state.step;
this.setState({ step: step - 1 });
}
onChange(e) {
this.setState({ [e.target.name]: e.target.value });
}
onSubmit(e) {
e.preventDefault();
}
render() {
const { step } = this.state;
const { risikoName, risikoBereich, risikoKlasse, branche } = this.state;
const values = { risikoName, risikoBereich, risikoKlasse, branche };
switch (step) {
case 1:
return (
<RiskDetails
nextStep={this.nextStep}
onChange={this.onChange}
values={values}
getBranch={getBranch}
/>
);
}
}
}
RiskForm.propTypes = {
getBranch: PropTypes.func.isRequired
};
export default connect(
state => {
return {};
},
{ getBranch }
)(RiskForm);
my riskDetails
import React, { Component } from "react";
import { Grid, Form } from "semantic-ui-react";
import PropTypes from "prop-types";
class RiskDetails extends Component {
componentWillMount() {
this.props.getBranch().then(res => {
console.log(res);
});
}
render() {
const { value } = this.props;
return (
<Grid container>
<Grid.Row>
<Grid.Column width={8}>
<Form>
<Form.Select
fluid
label={value.branch}
//options={this.state.branch}
placeholder={value.branch}
/>
</Form>
</Grid.Column>
</Grid.Row>
</Grid>
);
}
}
RiskDetails.propTypes = {
getBranch: PropTypes.func.isRequired
};
export default RiskDetails;
my actions
import axios from "axios";
import { FETCH_BRANCH } from "./types";
export function createRisk(event) {
return dispatch => {
return axios.post("/api/risk", event);
};
}
export function fetchRiskDetails(risk) {
return {
type: FETCH_BRANCH,
risk
};
}
export function getBranch() {
return dispatch => {
console.log("starting get request")
return axios.get("/api/risk").then(res => {
dispatch(fetchRiskDetials(res.data));
console.log(res.data);
});
};
}
reducer
import { FETCH_BRANCH } from "../actions/types";
const initialState = {
risk: {}
};
export default (state = initialState, action = {}) => {
switch (action.type) {
case FETCH_BRANCH:
return {
risk: action.risk
};
default:
return state;
}
};
server index.js
import express from "express";
import path from "path";
import bodyParser from "body-parser";
import mongoose from "mongoose";
import cors from "cors";
//WEBPACK
import webpack from "webpack";
import webpackMiddleware from "webpack-dev-middleware";
import webpackHotMiddleware from "webpack-hot-middleware";
import wepbackConfig from "../webpack.config.dev";
//IMPORT ALL API ROUTES
import authRoute from "./routes/auth";
import signUpRoute from "./routes/signup";
import companyRoute from "./routes/company";
import riskRoute from "./routes/risk";
import recommendationRoute from "./routes/recommendation";
let app = express();
const compiler = webpack(wepbackConfig);
app.use(
webpackMiddleware(compiler, {
hot: true,
publicPath: wepbackConfig.output.publicPath,
noInfo: true
})
);
app.use(cors());
app.use(express.static(path.join(__dirname, "dist")));
app.use(webpackHotMiddleware(compiler));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
// SET UP YOUR DB FOR THE API
// =============================================================================
var url = "mongodb://localhost:27017/blubb";
mongoose
.connect(
url,
{ useNewUrlParser: true, useCreateIndex: true }
)
.then(() => console.log("connected to DB"))
.catch(err => console.log(err));
app.get("/*", (req, res) => {
res.sendFile(path.join(__dirname, "./index.html"));
});
// ROUTES FOR OUR API
// =============================================================================
app.use("/api/auth", authRoute);
app.use("/api/risk", riskRoute);
app.listen(3000, () => console.log("running"));
risk part
import express from "express";
import {
validatePostInput,
validateDeleteInput
} from "../shared/validation/risk";
import Risk from "../models/risk";
import authenticate from "../middleware/authenticate";
let router = express.Router();
router.get("/", (req, res) => {
console.log("hi form server");
Risk.find(function(err, risks) {
if (err) {
console.log(err);
} else {
res.status(400).json(risks);
}
});
});
//create new risk
router.post("/", authenticate, (req, res) => {
const { errors, isValid } = validatePostInput(req.body);
console.log(req.body);
if (isValid) {
const { risikoName, risikoBereich, risikoKlasse, createdBy } = req.body;
var newRisk = new Risk({
risikoName,
risikoBereich,
risikoKlasse,
createdBy
});
// save the risk
newRisk.save(function(err) {
if (err) {
return res.status(500).json({ error: err });
}
res.json({ success: true });
});
} else {
res.status(400).json(errors);
}
});
//delete risk
router.delete("/", (req, res) => {
const { errors, isValid } = validateDeleteInput(req.body);
if (isValid) {
const { id } = req.body;
Risk.remove({ _id: id }, function(err, risk) {
if (err) return res.status(500).json({ error: err });
res.json({ success: true });
});
} else {
res.status(400).json(errors);
}
});
export default router;
webpack config
import path from "path";
import webpack from "webpack";
export default {
mode: "development",
entry: [
"webpack-hot-middleware/client",
path.join(__dirname, "client/index.js")
],
output: {
filename: "bundle.js",
path: "/",
publicPath: "/"
},
plugins: [
new webpack.NoEmitOnErrorsPlugin(),
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin()
],
module: {
rules: [
{
test: /\.js$/,
include: [
path.join(__dirname, "client"),
path.join(__dirname, "server/shared")
],
loaders: ["react-hot-loader/webpack", "babel-loader"]
},
{
test: /\.css$/,
loader: "style-loader!css-loader"
},
{
test: /\.s[a|c]ss$/,
loader: "sass-loader!style-loader!css-loader"
},
{
test: /\.(jpg|png|gif|jpeg|woff|woff2|eot|ttf|svg)$/,
loader: "url-loader?limit=100000"
}
]
},
resolve: {
extensions: [".js"]
}
};
===EDIT===
Here is my response from a request
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Tool</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
</head>
<body>
<div id="app"></div>
<script src="bundle.js"></script>
</body>
</html>
First, the reason why you always get whole HTML page, I guess it caused by routing management. Looks like the part when you using asterisk/wild card in your route as follows:
app.get("/*", (req, res) => {
res.sendFile(path.join(__dirname, "./index.html"));
});
Looks it will handle all requests, so router describe after that will never get reached. Try to change it with:
app.get("/", (req, res) => {
res.sendFile(path.join(__dirname, "./index.html"));
});
If you really need it to have /* put another one at very last line of router declaration.
2nd:
You call props inside componentWillMount(), which usually props not yet available at that lifecycle. Even usually it still not available in componentDidMount() lifecycle. Based on some articles I read, componentWillMount() is deprecated in current React version. I suggest you to not using it. There are 2 ways to solve this kind of problem I can recommend you as follow:
componentDidMount() method
Using componentDidMount() lifecycle. In this method, you should put setTimeout() to give the component a time to receive props.
componentDidMount() {
setTimeout(()=>{
this.props.getBranch().then(res => {
console.log(res);
});
},
100);//Try with 0 first ( not put this parameter ), if it is not works, try to increase by 100, 200 etc.
}
2. getDerivedStateFromProps() method
The old way is using componentWillRecieveProps(), but because it is deprecated, I recommend to use getDerivedStateFromProps() instead. In this life cycle phase, it doesn't need setTimeout() trick. Because props should already available.
For your reference, here is good article about deprecated React Livecycles: https://hackernoon.com/problematic-react-lifecycle-methods-are-going-away-in-react-17-4216acc7d58b
==== UPDATE Jan 22 '19 ======
Just a suggestion, usually we can just call redux actions directly to the component instead passing them as props. So you can clean up RiskForm component from Redux wiring as follow:
import React, { Component } from "react";
// import { connect } from "react-redux"; //!!! move connect() inside RiskDetails instead
import PropTypes from "prop-types";
import RiskDetails from "./Step1/RiskDetails";
// import { getBranch } from "../../actions/riskActions"; //!!!!Move this to RiskDetails component
class RiskForm extends Component {
constructor(props) {
super(props);
this.state = {
step: 1,
risikoName: "",
risikoBereich: "",
risikoKlasse: "",
branche: ""
};
this.onChange = this.onChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
}
nextStep() {
let step = this.state.step;
this.setState({ step: step + 1 });
}
prevStep() {
let step = this.state.step;
this.setState({ step: step - 1 });
}
onChange(e) {
this.setState({ [e.target.name]: e.target.value });
}
onSubmit(e) {
e.preventDefault();
}
render() {
const { step } = this.state;
const { risikoName, risikoBereich, risikoKlasse, branche } = this.state;
const values = { risikoName, risikoBereich, risikoKlasse, branche };
switch (step) {
case 1:
return (
<RiskDetails
nextStep={this.nextStep}
onChange={this.onChange}
values={values}
// getBranch={getBranch}//!!! Don't need this props, import inside RiskDetails directly
/>
);
}
}
}
RiskForm.propTypes = {
getBranch: PropTypes.func.isRequired
};
/*
export default connect(
state => {
return {};
},
{ getBranch }
)(RiskForm);*/
export default RiskForm;//You can remove connect() and leave this as dumb component
Then make your RiskDetails component as Container/smart component as follow ( please pay attention at comments I made with exclamation/'!' ):
import React, { Component } from "react";
import {connect} from 'react-redux';//!!! Import redux here
import { Grid, Form } from "semantic-ui-react";
import PropTypes from "prop-types";
import { getBranch } from "../../actions/riskActions";//!!! Import action creator here
class RiskDetails extends Component {
componentWillMount() {
getBranch().then(res => {//Call the action creator directly instead using props
console.log(res);
});
}
render() {
const { value } = this.props;
return (
<Grid container>
<Grid.Row>
<Grid.Column width={8}>
<Form>
<Form.Select
fluid
label={value.branch}
//options={this.state.branch}
placeholder={value.branch}
/>
</Form>
</Grid.Column>
</Grid.Row>
</Grid>
);
}
}
RiskDetails.propTypes = {
getBranch: PropTypes.func.isRequired
};
export default connect(null)(RiskDetails);//!!! Connect this component with redux

How to rehydrate my apollo state from server side?

I am totally new to react-apollo I am pretty confused that how to rehydrate state from the server side to client And my app is working, But the problem is it is not using preloaded state from Apollo After component rendered it is calling the API again.
Seriously Redux Integration Makes Complicated only Apollo state is rendering not the custom redux state that's the problem here.But I don;t know how to integrate.
Server.js
const HTML = ({ html,state}) => (
<html lang="en" prefix="og: http://ogp.me/ns#">
<head>
<meta charSet="utf-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta httpEquiv="Content-Language" content="en" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<div
id="app"
dangerouslySetInnerHTML={{ __html: html }} />
<script dangerouslySetInnerHTML={{
__html: `window.__STATE__=${JSON.stringify(state)};`,
}} />
<script src="/static/app.js" />
</body>
</html>
);
app.get('/*',(req,res) => {
const routeContext = {};
const client = serverClient();
const components = (
<StaticRouter location={req.url} context={routeContext}>
<ApolloProvider store={store} client={client}>
<WApp />
</ApolloProvider>
</StaticRouter>
);
getDataFromTree(components).then(() => {
const html = ReactDOMServer.renderToString(components);
const initialState = {apollo: client.getInitialState()}
res.send(`<!DOCTYPE html>\n${ReactDOMServer.renderToStaticMarkup(
<HTML
html={html}
state={initialState}
/>,
)}`)
})
})
apolloClient.js
import ApolloClient, {
createNetworkInterface,
addTypeName,
} from 'apollo-client';
const isProduction = process.env.NODE_ENV !== 'development';
const testUrl = 'http://localhost:3000/api';
// const url = isProduction ? productionUrl : testUrl;
const url = testUrl;
const client = new ApolloClient({
networkInterface: createNetworkInterface({uri:testUrl}),
dataIdFromObject:({id}) => id,
reduxRootKey:state => state.apollo,
initialState: (typeof window !=='undefined')? window.__STATE__:{}
});
export default client;
store.js
import { createStore, compose, applyMiddleware } from 'redux';
import { syncHistoryWithStore } from 'react-router-redux';
import thunk from 'redux-thunk';
import {createLogger} from 'redux-logger';
import client from '../apolloClient';
import rootReducer from '../Reducers'
//All Reducer
import {initialState as allPosts} from '../Reducers/AllPosts_Reucer';
const isProduction = process.env.NODE_ENV !== 'development';
const isClient = typeof document !== 'undefined';
const initialState = {
allPosts
};
const middlewares = [thunk, client.middleware()];
const enhancers = [];
if (!isProduction && isClient) {
const loggerMiddleware = createLogger();
middlewares.push(loggerMiddleware);
if (typeof devToolsExtension === 'function') {
const devToolsExtension = window.devToolsExtension;
enhancers.push(devToolsExtension());
}
}
const composedEnhancers = compose(
applyMiddleware(...middlewares),
...enhancers
);
const store = createStore(
rootReducer,
initialState,
composedEnhancers,
);
export default store;
Sample Component
import React,{Component} from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import * as postActions from '../../Redux/Actions/postActions';
class Home extends Component{
componentWillMount(){
// console.log('From Will Mount',this.props.posts)
}
renderAllPost(){
const {loading,posts} = this.props;
if(!loading){
return posts.map(data => {
return <li key={data.id}>{data.title}</li>
})
}else{
return <div>loading</div>
}
}
render(){
console.log(this.props);
return(
<div>
{this.renderAllPost()}
</div>
)
}
}
//start from here
const GetallPosts = gql`
query getAllPosts{
posts{
id
title
body
}
}
`;
// const mapStateToPros = (state) => ({
// allPosts:state.allPosts
// });
const mapDispatchToProps = (dispatch) => ({
actions:bindActionCreators(
postActions,
dispatch
)
});
const ContainerWithData = graphql(GetallPosts,{
props:({ data:{loading,posts} }) => ({
posts,
loading,
})
})(Home)
export default connect(
// mapStateToPros,
// mapDispatchToProps
)(ContainerWithData)
You can inject the redux-persist state directly into apollo-client with
getStoredState({ storage: localforage }, (err, rehydratedState) => { ... }
also i wish there was a different approach, check for Delay Render Until Rehydration Complete

Categories