Mateusz
Mateusz

Reputation: 1193

React+Express - refresh page when fetching new data

I would like to refresh infos on client side, when I get POST request with data on server.

Currently it looks like this. I have POST request on server-side:

 app.post('/login',jsonParser,  function (req, res) {
  if (!req.body) return res.sendStatus(400);
  if(req.body){
    console.log(req.body);
    mongodb.connect(dbUrl,function(err,db){
      db.collection('markers').insertOne(req.body);
    });
    return res.sendStatus(200);
  }
})

When I get succesful insertion to database I would like to refresh client-side automatically. To display infos from markers' JSON from database I am using MarkersList component and MarkerPage component:

MarkersList.js

 export default function MarkersList({markers})  {
  const emptyMessage=(
    <p> There are no coordinates to display </p>
  );

  const markersList=(
    <div>{markers.map(marker=> <p>{marker.title}</p>)}</div>
  );

  return(
    <div>
      {markers.length === 0 ? emptyMessage:markersList}
    </div>
  );
}

MarkersList.propTypes={
  markers:React.PropTypes.array.isRequired
}

MarkerPage.js

import React from 'react';
import MarkersList from './MarkersList';
import {connect} from 'react-redux';
import {fetchMarkers} from './actions';

class Markers extends React.Component{
  componentDidMount(){
    this.props.fetchMarkers();
  }

  render(){
    return(
      <div>
        <p>LISTA</p>
        <MarkersList markers={this.props.markers}/>
      </div>
    )
  }
}


Markers.propTypes={
  markers: React.PropTypes.array.isRequired,
  fetchMarkers: React.PropTypes.func.isRequired
}

function mapStateToProps(state){
  return{
    markers:state.markers
  }
}

export default connect(mapStateToProps,{fetchMarkers})(Markers);

actions.js

export const SET_MARKERS='SET_MARKERS';

export function setMarkers(markers){
  return{
    type:SET_MARKERS,
    markers
  }
}

export function fetchMarkers(){
  return dispatch => {
    fetch('/api/markers')
      .then(res=>res.json())
      .then(data=>dispatch(setMarkers(data.markers)));
    ;
  }
}

index.js -client side:

    const store= createStore(
  rootReducer,
  composeWithDevTools(
    applyMiddleware(thunk) //dispach asynchronous actions
  )
)

render(
  <BrowserRouter>
  <Provider store={store}>
      <div>
          <Route path="/" component={App}></Route>
          <Route  path="/markers" component={Markers}></Route>
        </div>
        </Provider >
      </BrowserRouter>

      ,document.getElementById('app'));

What should I do to emit something like action from server-side that will allow me to display current state of database on the client-side?

Upvotes: 1

Views: 2597

Answers (1)

Julien TASSIN
Julien TASSIN

Reputation: 5212

You have several way to perform client-side update driven by server-side :

client-side timeout or polling request

Pretty unsatisfying because it asks you to make prediction about the execution time of the server-side code and it is not really driven by the server. Here are two solutions to perform such a think :

In the MarkerPage Component with a mixin

Just use a mixin like react-timer-mixin to call this.props.fetchMarkers in MarkerPage component every X seconds (2 seconds in the bellow example).

import React from 'react';
import TimerMixin from 'react-timer-mixin'; 
import MarkersList from './MarkersList';
import {connect} from 'react-redux';
import {fetchMarkers} from './actions';

class Markers extends React.Component{
 componentDidMount(){
    this.props.fetchMarkers();
      TimerMixin.setTimeout(() => { 
       this.props.fetchMarkers();
      },2000);
  }

  render(){
    return(
      <div>
        <p>LISTA</p>
        <MarkersList markers={this.props.markers}/>
      </div>
    )
  }
}


Markers.propTypes={
  markers: React.PropTypes.array.isRequired,
  fetchMarkers: React.PropTypes.func.isRequired
}

function mapStateToProps(state){
  return{
    markers:state.markers
  }
}

export default connect(mapStateToProps,{fetchMarkers})(Markers);

To not try to avoid the mixin it assures you that the timer will be destroyed with the component.

In my opinion, it's not the component purpose to ensure the data-reload. Its only purpose is to display.

In a middleware or external module.

To avoid the data-reload from the component, I prefer the use of an independant code that will dispatch to the store. It won't interfer with the component code. Here is one example adapted from my application :

index.js -client side:

import {fetchMarkers} from './actions';

function startAutoReloadMarkers(store) {
  window.setInterval(() => {
    store.dispatch(fetchMarkers());
  }, 2000);
}

const store= createStore(
  rootReducer,
  composeWithDevTools(
    applyMiddleware(thunk)
  )
)

startAutoReloadMarkers(store)

Second solution. Using a socket and be in a really real-time environment.

It's a very good way to profit from the react/redux advantages. Here is the solution I used in my application :

In your server-side login code :

// On user login (using a websocket), I trigger a call to an external service wich will respond later (my seneca service is the equivalent of your mongodb insert)
seneca().use(senecaTransactions).act({ cmd: handleAutomaticOperations, userId: user._id }, (error, transactions) => {
  if(error) {
    // You don't need thios
    reject(error);
  } else {
    // You don't need this
    resolve(true);
    // Here I will notify the client that some transactions have been updated/added
    socket.emit(TRANSACTION_UPDATED, transactions);
  }
});

index.js -client side:

import Socket from '../Socket';
import { TRANSACTION_UPDATED } from '../constants/SocketActions';

export function startTransactionsMiddleware(store) {
  Socket.on(TRANSACTION_UPDATED, (transaction) => {
    // The equivalent of your dispatch(fetchMarkers()
    store.dispatch(transactionUpdated(transaction));
  });      
}

// Same think that the previous example
startTransactionsMiddleware(store);

This way once the TRANSACTION_UPDATED is trigerred in the server my display will be updated thanks to redux.

To not hesitate if you need some help for implementing the second solution.

Upvotes: 1

Related Questions