I have a requirement in which I am rendering view in which I display a form. On submit of form i need to gather the form data and create a file and save form data as JSON in that file. I am using React.js, node.js, babel and webpack.
After struggling a bit to achieve this I figured out that I have to use isomorphic or universal javascript i.e use react and render on server side as we cannot use fs module on client side. Referred this for server side.
i run it using: npm run start
After this I can see in console that [Object Object]
is printed on console from Line 1 in below react component (HomePage.js). But later on when I access this page it gives an error:
'bundle.js:18 Uncaught Error: Cannot find module "fs"'
How can i use fs module with react ?
Below are code snippets :
webpack.config.js
package.json
{
"name": "sample",
"version": "1.0.0",
"description": "Simple application to showcase how to achieve universal rendering and routing with React and Express.",
"main": "src/server.js",
"scripts": {
"start": "SET NODE_ENV=production&&babel-node src/server.js",
"start-dev": "npm run start-dev-hmr",
"start-dev-single-page": "node_modules/.bin/http-server src/static",
"start-dev-hmr": "webpack-dev-server --progress --inline --hot",
"build": "SET NODE_ENV=production&&webpack -p"
},
"dependencies": {
"babel-cli": "^6.11.4",
"babel-core": "^6.13.2",
"babel-loader": "^6.2.5",
"babel-plugin-react-html-attrs": "^2.0.0",
"babel-preset-es2015": "^6.13.2",
"babel-preset-react": "^6.11.1",
"babel-preset-react-hmre": "^1.1.1",
"ejs": "^2.5.1",
"express": "^4.14.0",
"react": "^15.3.1",
"react-dom": "^15.3.1",
"react-router": "^2.6.1"
},
"devDependencies": {
"http-server": "^0.9.0",
"react-hot-loader": "^1.3.0",
"webpack": "^1.13.2",
"webpack-dev-server": "^1.14.1"
}
}
server.js
use strict';
import path from 'path';
import { Server } from 'http';
import Express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { match, RouterContext } from 'react-router';
import routes from './routes';
import NotFoundPage from './components/NotFoundPage';
//import fs from 'fs';
//console.log("server" + fs);
// initialize the server and configure support for ejs templates
const app = new Express();
const server = new Server(app);
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
// define the folder that will be used for static assets
app.use(Express.static(path.join(__dirname, 'static')));
// universal routing and rendering
app.get('*', (req, res) => {
match(
{ routes, location: req.url },
(err, redirectLocation, renderProps) => {
//console.log("renderProps "+ Object.values(routes));
//console.log("req.url "+ req.url);
// in case of error display the error message
if (err) {
return res.status(500).send(err.message);
}
// in case of redirect propagate the redirect to the browser
if (redirectLocation) {
return res.redirect(302, redirectLocation.pathname + redirectLocation.search);
}
// generate the React markup for the current route
let markup;
if (renderProps) {
// if the current route matched we have renderProps
markup = renderToString(<RouterContext {...renderProps}/>);
} else {
// otherwise we can render a 404 page
markup = renderToString(<NotFoundPage/>);
res.status(404);
}
// render the index template with the embedded React markup
return res.render('index', { markup });
}
);
});
// start the server
const port = process.env.PORT || 3000;
const env = process.env.NODE_ENV || 'production';
console.log(`Server starting on http://localhost:${port} [${env}]`)
server.listen(port, err => {
if (err) {
return console.error(err);
}
console.info(`Server running on http://localhost:${port} [${env}]`);
});
HomePage.js (React component)
import React from 'react';
import fs from 'fs';
import dateformat from 'dateformat';
console.log("home page" + fs); -- Line 1
class HomePage extends React.Component{
checkDirectory(directory, callback) {
fs.stat(directory, function(err, stats) {
//Check if error defined and the error code is "not exists"
if (err && err.errno === 34) {
//Create the directory, call the callback.
fs.mkdir(directory, callback);
} else {
//just in case there was a different error:
callback(err)
}
});
}
handleClick(){
var obj = JSON.stringify($('#statusForm').serializeArray());
this.checkDirectory("directory/"+currentDate, function(error) {
if(error) {
console.log("oh no!!!", error);
} else {
//Carry on, all good, directory exists / created.
fs.writeFile("directory/"+currentDate+name+".json", obj, function(err) {
if(err) {
return console.log(err);
}
console.log("The file was saved!");
});
console.log("exists");
}
});*/
}
render() {
return (
<div className="container">
<form id="statusForm" className="form-horizontal" >
<div className="form-group">
<label className="control-label col-sm-2" for="names">Select list:</label>
<div className="col-sm-10">
<select name="names" className="form-control" id="names">
<option>Select</option>
<option>abc</option>
<option>xyz</option>
</select>
</div>
</div>
<div className="form-group">
<label className="control-label col-sm-2" for="team">Select list:</label>
<div className="col-sm-10">
<select name="team" className="form-control" id="team">
<option>Select</option>
<option>team 1</option>
<option>team 2</option>
</select>
</div>
</div>
<div className="form-group">
<label className="control-label col-sm-2" for="pwd">Password:</label>
<div className="col-sm-10">
<input type="textarea" className="form-control" id="todayTask" name="todayTask" placeholder="Enter Task"/>
</div>
</div>
<div className="form-group">
<div className="col-sm-offset-2 col-sm-10">
<button type="button" className="btn btn-default" onClick={this.handleClick.bind(this)}>Submit</button>
</div>
</div>
</form>
</div>
);
}
}
export default HomePage;
EDIT 1:
I investigated more and found out that if i do not build my app explicitly using npm run build and just update my react component i do not get above error. Also, after this if i put file creation logic directly inside render method and on refreshing page it successfully create a file. So observation is it does not work with Onclick of button and can work if we refresh the page. it goes to server and thats why it works this way.
EDIT 2:
Page refresh issue resolved by using target:'node' in my webpack config but I do get the error:
Uncaught ReferenceError: require is not defined
In browser.so file creation logic directly inside render method will create the file the moment we access the page. No refresh required.
Can anyone guide me what is the best way to achieve my desired requirement?
First let's go through your errors a little bit:
When you don't use npm run build
or npm run start
, you won't use webpack and therefore the require
statement doesn't get replaced with the contents of the fs
module--instead you are left with a require statement, which your browser doesn't understand since require is a Node-only function. Thus, your error about require not being defined.
If you do run with npm run build
or npm run start
, webpack takes that require statement out and replaces it with the fs
module. But, as you've discovered, fs
doesn't work on the client side.
So, if you can't use fs
to save files, what can you do?
If you are trying to save the file to a server, you have to submit data from your form to the Node server, and the Node server can use fs
to interact with the server's filesystem to save the file.
If you are trying to save the form locally, i.e., on the same device as the browser, you need to use another strategy like this or by using a client-side library like FileSaver. Which option you take depends somewhat on your use case, but if you are trying to save on the client side you can search around "saving files from web browser" or "saving files client side" to see what works for you.