Hi Im new to React & nodeJS, i'm trying to call my nodejs api through react and my componentDidMount is not triggered at all even after rendering the page.
Can some one please give some idea on where exactly I might be missing.
var React = require('react');
module.exports = React.createClass({
getInitialState: function() {
return {
jobs: []
}
},
componentDidMount: function() {
console.log("mount");
var _this = this;
this.serverRequest = $.ajax({
....
}.bind(this)
});
},
componentWillUnmount: function() {
this.serverRequest.abort();
},
render: function() {
return (
<div><h1>Jobs</h1>
{this.state.jobs.map(function(job) {
return (
<div key={job.id} className="job">
{job.name}
{job.address}
</div>
);
})}
</div>
)
}
});
In my NodeServer.js file i'm calling in this way, Only Jobs is being displayed in my html page
app.get('/', function(request, response) {
var htmlCode = ReactDOMServer.renderToString(React.createElement(Component));
response.send(htmlCode);
})
You can't use the React component lifecycle methods to load data when you're rendering in the backend, this will only work in the client. When you render the component on the server, it is only rendered once, without the data. The data is loaded asynchronously, when it is finished, your component has already been rendered. You have to fetch your data outside the component, then pass it to the component as a prop.
Related
I'm making a tapTracker app with MEVN stack and there is a page with all of songs
and i want that when i click on a single song make app to open this single song only ...
IN THE FRONT END ==>
so i make route from songs.vue '{that songs page}' to viewSong.vue
<v-btn dark class="cyan darken-3" v-on:click="navigateTo({name: 'song',
params: {songId: song._id}})"> View </v-btn>
after that in viewsong.vue i make a show function with id param
<template>
<h1></h1>
</template>
<script>
import songsServices from '#/services/songsServices'
export default {
data () {
return {
song: null
}
},
async mounted () {
const songId = this.$store.state.route.params.songId
this.song = await songsServices.show(songId)
}
}
</script>
then i make an api with it to send that to back-end server in
songServices.js
show (songId) {
return Api().get(`songs/${songId}`)
}
IN THE BACK END 'SERVER' ==>
i make get http req in router.js
app.get('/songs/:songId', songsController.show)
songsController.js
async show(req,res) {
try {
const Song = await Songs.findOne({_id: req.params.songId})
res.send(song)
} catch (error) {
res.status(400).send('error: error when tring to fetch songs!!!')
console.log(error)
}
}
in postman when i make a get req with this param it give me error
where is the problem ??
hint this is the project url on github
https://github.com/mohamedAdel01/tapTracker
I want to receive data via a REST service call in my React Universal (with Next.js) app using fetch() and then render the result into JSX like this:
class VideoPage extends Component {
componentWillMount() {
console.log('componentWillMount');
fetch(path, {
method: 'get',
})
.then(response =>
response.json().then(data => {
this.setState({
video: data,
});
console.log('received');
})
);
}
render() {
console.log('render');
console.log(this.state);
if (this.state && this.state.video) {
return (
<div>
{this.state.video.title}
</div>
);
}
}
}
export default VideoPage;
Unfortunately, the output is this:
componentWillMount
render
null
received
Which does make sense because the call to fetch is asynchronously and render() finishes before the call to the REST service has finished.
In a client-side app this would be no problem because a change of state would call render() which then updates the view, but in a universal app, especially with JavaScript turned off on the client, this is not possible.
How can I solve this?
Is there a way to call the server synchronously or delay render()?
In order to get it working, I had to do 3 things:
Replace componentWillMount with getInitialProps() method
Combine fetch with await and return the data
Use this.props instead of this.state
Code looks like this now:
static async getInitialProps({ req }) {
const path = 'http://path/to/my/service';
const res = await fetch(path);
const json = await res.json();
return { video: json };
}
Then, in render() I can access the data via this.props.video, for example:
render() {
return (
<div>{this.props.video.title}</div>
);
}
You can add static async getInitialProps () {} to load data into props before the page component gets rendered.
More info here: https://github.com/zeit/next.js/blob/master/readme.md#fetching-data-and-component-lifecycle
My front-end page is made by React + Flux, which sends the script data to back-end nodejs server.
The script data is an Array which contains the linux shell arguments (more than 100000). When to back-end received, it will execute the linux shell command.
Just an example:
cat ~/testfile1
cat ~/testfile2
.
.
.
(100000 times ...etc)
When the backend finished one of the linux shell commands, I can save the readed content to result data. Therefore, socket.io will emit the result data to the front-end.
I want to get the result data from my webpage in real time, so I have done some stuff in my project below.
My React component code:
import React from 'react';
import AppActions from '../../../actions/app-actions';
import SocketStore from '../../../stores/socket-store';
import ResultStore from '../../../stores/result-store';
function getSocket () {
return SocketStore.getSocket();
}
function getResult () {
return ResultStore.getResultItem();
}
class ListResultItem extends React.Component {
constructor () {
super();
}
render () {
return <li>
{this.props.result.get('name')} {this.props.result.get('txt')}
</li>;
}
}
class ShowResult extends React.Component {
constructor () {
super();
this.state = {
socket: getSocket(),
result: getResult()
};
}
componentWillMount () {
ResultStore.addChangeListener(this._onChange.bind(this));
}
_onChange () {
this.setState({
result: getResult()
});
}
render () {
return <div>
<ol>
{this.state.result.map(function(item, index) {
return <ListResultItem key={index} result={item} />;
})}
</ol>
</div>;
}
componentDidMount () {
this.state.socket.on('result', function (data) {
AppActions.addResult(data);
});
}
}
My Flux store (ResultStore) code:
import AppConstants from '../constants/app-constants.js';
import { dispatch, register } from '../dispatchers/app-dispatcher.js';
import { EventEmitter } from 'events';
import Immutable from 'immutable';
const CHANGE_EVENT = 'changeResult';
let _resultItem = Immutable.List();
const _addResult = (result) => {
let immObj = Immutable.fromJS(result);
_resultItem = _resultItem.push(immObj);
}
const _clearResult = () => {
_resultItem = _resultItem.clear();
}
const ResultStore = Object.assign(EventEmitter.prototype, {
emitChange (){
this.emit( CHANGE_EVENT );
},
addChangeListener (callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener (callback) {
this.removeListener(CHANGE_EVENT, callback);
},
getResultItem () {
return _resultItem;
},
dispatcherIndex: register(function (action) {
switch (action.actionType) {
case AppConstants.ADD_RESULT:
_addResult(action.result);
break;
case AppConstants.CLEAR_RESULT:
_clearResult();
break;
}
ResultStore.emitChange();
})
});
However, the page will become very slow after rendering more than 1000 datas. How to enhance the performance for rendering? I need to execute the linux script persistently more than 3 days. Any solutions? Thanks~
Is there any need to render all the data on screen? If not then there are a few ways to deal with cutting down the amount of visible data.
Filter / Search
You can provide a search/filter component that complements the list and creates a predicate function that can be used to determine whether each item should or should not be rendered.
<PredicateList>
<Search />
<Filter />
{this.state.result
.filter(predicate)
.map(function(item, index) {
return <ListResultItem key={index} result={item} />;
})
}
</PredicateList>
Lazy Load
Load the items only when they are asked for. You can work out whether item is needed by keeping track of whether it would be onscreen, or whether the mouse was over it.
var Lazy = React.createClass({
getInitialState: function() {
return { loaded: false };
},
load: function() {
this.setState({ loaded: true });
},
render: function() {
var loaded = this.state.loaded,
component = this.props.children,
lazyContainer = <div onMouseEnter={this.load} />;
return loaded ?
component
lazyContainer;
}
});
Then simply wrap your data items inside these Lazy wrappers to have them render when they are requested.
<Lazy>
<ListResultItem key={index} result={item} />
</Lazy>
This ensures that only data needed by the user is seen. You could also improve the load trigger to work for more complex scenarios, such as when the component has been onscreen for more then 2 seconds.
Pagination
Finally, the last and most tried and tested approach is pagination. Choose a limit for a number of data items that can be shown in one go, then allow users to navigate through the data set in chunks.
var Paginate = React.createClass({
getDefaultProps: function() {
return { items: [], perPage: 100 };
},
getInitialState: function() {
return { page: 0 };
},
next: function() {
this.setState({ page: this.state.page + 1});
},
prev: function() {
this.setState({ page: this.state.page - 1});
},
render: function() {
var perPage = this.props.perPage,
currentPage = this.state.page,
itemCount = this.props.items.length;
var start = currentPage * perPage,
end = Math.min(itemCount, start + perPage);
var selectedItems = this.props.items.slice(start, end);
return (
<div className='pagination'>
{selectedItems.map(function(item, index) {
<ListResultItem key={index} result={item} />
})}
<a onClick={this.prev}>Previous {{this.state.perPage}} items</a>
<a onClick={this.next}>Next {{this.state.perPage}} items</a>
</div>
);
}
});
These are just very rough examples of implementations for managing the rendering of large amounts of data in efficient ways, but hopefully they will make enough sense for you to implement your own solution.
I'm writing a very simple Nodejs app. I use React + Socket.io.
There's a root element which immediately renders another react component (you may wonder why I have this root element. The reason is that I want to be able to mount one of the two components after receiving a message from server, but for the start I render a preselected component).
In this root component , I define a socket in componentDidMount . Now the problem is that I want to pass this socket to all of the children (so they can listen and communicate with the server messages.) But if I connect to the server in componentDidMount of the root, during the rendering there is no socket as it's not connected yet and null will be passed to the child components.
'use strict';
var React = require('react');
var ioClient = require('socket.io-client');
var UsersList = require('./usersList');
var Game = require('./game');
var socket;
var Snake = React.createClass({
displayName: 'Snake',
propTypes: {},
getDefaultProps: function() {
return {};
},
mixins: [],
getInitialState: function() {
return ({
usersList: true,
game: false
});
},
componentWillMount: function() {
},
componentWillUnmount: function() {
this.socket.close();
},
componentDidMount: function() {
socket = ioClient.connect(); // this happens after render
},
render: function() {
var result = null;
if (this.state.usersList) {
result = <UsersList socket={socket}/> // therefore this one is passed as null
} else { //game : true
result = <Game socket={socket}/>
}
return (<div>
{result}
</div>)
}
});
module.exports = Snake;
'use strict';
var React = require('react');
var UsersList = React.createClass({
displayName: 'UsersList',
propTypes: {},
getDefaultProps: function() {
return {};
},
mixins: [],
getInitialState: function() {
return ({
usersList:[]
});
},
componentWillReceiveProps: function(){
},
componentWillMount: function() {
},
componentWillUnmount: function() {
},
componentDidMount: function(){
var socket = this.props.socket; // this one was passed into the component as null
socket.on('usersList', function(data){ // so this one returns an error
this.setState({
usersList: data.usersList
});
});
},
render: function() {
var users = [];
for (var i = 0 ; i < this.state.usersList.length ; i++){
users.push(<span>{this.state.usersList[i]}</span>);
}
return(<div>{users}</div>);
}
});
module.exports = UsersList;
So , now you may ask why I don't put io.connect() in componentWillMount or at the top of the file. Well , it doesn't work ! it returns this error : Cannot find property "protocol" ....
I cannot put it in render , componentWillMount , top of the file ...
Any idea on how to do this ?
You could continue to connect in componentDidMount. It will not be immediately available to the component's children, but then you could do something like this in the children:
componentDidUpdate(prevProps, prevState) {
if ( this.props.socket ) {
// do your connection logic here
}
}
This will ensure that the children immediately connect when the socket is first connected and available to them. Inside the if statement you could also verify that this.props.socket is not equal to prevProps.socket to prevent a redundant connection attempt.
In my app I have layouts and views for those layouts. Layouts only change on login/logout, but the other special case is on pageload I need to load the proper layout. However, in my defaultAction my layout does not actually render after it returns and so when the view tries to render, the el it is supposed to be contained in does not exist.
// Filename: router.js
var app_router;
define( [ 'views/layouts/beta', 'views/beta/requestInvite', 'views/beta/login', 'views/app/dashboard' ],
function(betaLayout, requestInviteView, loginView, dashboardView) {
var AppRouter = Backbone.Router.extend( {
routes : {
// Pages
'login' : 'login',
'dashboard' : 'dashboard',
// Default
'*actions' : 'defaultAction'
},
// Pages
login : function() {
loginView.render();
},
dashboard : function() {
dashboardView.render();
},
// Default
defaultAction : function(actions) {
betaLayout.render();
requestInviteView.render();
}
});
var initialize = function() {
app_router = new AppRouter;
$('a').live('click', function() {
var href = $(this).attr('href');
// only navigate to real links
if(href == undefined)
return;
app_router.navigate(href, {trigger: true});
return false;
});
Backbone.history.start({pushState: true});
};
return {
initialize : initialize
};
});
How can I have my layout render completely before my view?
Define a callback for betaLayout.render() to take as an argument, that gets executed when the render is actually complete.
E.g., betaLayout.render() would look something like:
render: function(callback) {
/* code to render your element,
using the following line when all
asynchronous calls are complete */
if (callback) callback();
}
and your defaultAction would use it like so, passing the second render() as its callback.
betaLayout.render(requestInviteView.render);
The problem was that because my layout didn't render before my view was initialized, the el was empty. What I did was convert all of my objects to return the object instead of the instance, and let them render once they were initialized. This way, when I declare a new MyLayout and then a new MyView, I can be guaranteed that MyView's el is valid.