How to Handle Post Request in Isomorphic React + React Router Application - javascript

I want to build Isomorphic react + react-router application and after a few days googling, now I can achieve isomorphic application that only handles GET request.
Here's what I've done so far:
Server use react-router to handle all request
react-router will call fetchData functions that resides in each React View that matches the route.
Set the data fetched before into props of the React View and render it into string
Inject the string and data fetched before as global variable window.__STATE__ into HTML and deliver the HTML to the client
We have successfully render React App from the server
When the client finished loading our React App javascript, it will try to render. But we pass the state from window.__STATE__ as the props of our React App, and React will not re-render because the state is the same
The problem is it will not work with POST/PUT/DELETE/WHATEVER request. When handling GET request, react-router have information about params and query. For example if we have a route: /user/:uid and client request this url: /user/1?foo=bar, then params would be: {uid: 1} and query would be {foo: 'bar'}
react-router then can pass it down to fetchData function so it will know to fetch user with uid of 1 and do whatever with foo query.
While in POST request, react-router doesn't know about the POST parameters. On Server, of course we could pass the POST parameters to fetchData function, but what about the Client? It doesn't know what the POST parameters are.
Is there a way that the server could tell the Client about the POST parameters? Below is an example of my Login View. I want when user submit the form, the server will render error message on error, or redirect it to dashboard on success.
fetchData.js
import whenKeys from 'when/keys';
export default (authToken, routerState) => {
var promises = routerState.routes.filter((match) => {
return match.handler.fetchData;
}).reduce((promises, match) => {
promises[match.name] = match.handler.fetchData(authToken, routerState.params, routerState.query);
return promises;
}, {});
return whenKeys.all(promises);
}
server.js
...
app.use((req, res) => {
const router = Router.create({
routes,
location: req.originalUrl,
onError: next,
onAbort: (abortReason) => {
next(abortReason);
}
});
router.run((Handler, state) => {
fetchData(authToken, state).then((data) => {
// render matched react View and generate the HTML
// ...
})
});
});
...
login.jsx
import React from 'react';
import DocumentTitle from 'react-document-title';
import api from './api';
export default class Login extends React.Component {
constructor(props) {
super(props);
// how to fill this state with POST parameters on error?
// how to redirect on success?
// and remember that this file will be called both from server and client
this.state = {
error: '',
username: '',
password: ''
};
}
// I saw some people use this function, but it'll only work if
// the form's method is GET
static willTransitionTo(transition, params, query) {
// if only we could read POST parameters here
// we could do something like this
transition.wait(
api.post('/doLogin', postParams).then((data) => {
transition.redirect(`/dashboard`);
});
);
}
render() {
return (
<DocumentTitle title="Login">
<div className="alert alert-danger">{this.state.error}</div>
<form method="post">
<input type="text" name="username" value={this.state.username} onChange={this._onFieldChange('username')} placeholder="Username" /><br />
<input type="password" name="password" value={this.state.password} onChange={this._onFieldChange('password')} placeholder="Password" /><br />
<button type="submit">Login</button>
</form>
</DocumentTitle>
);
}
_onFieldChange(name) {
var self = this;
return (e) => {
e.preventDefault();
var nextState = {};
nextState[name] = e.target.value;
self.setState(nextState);
}
}
}

Getting "POST" data on the client
On the client side, you get POST data by extracting values from your form inputs in a way which corresponds to what you would have received on the server had the form been submitted normally.
Using POST data
So now you have your POST data, but you still have the problem that there's no way to feed the POST data into your transition hooks in React Router 0.13.x and earlier. I created a pull request for this feature which has now been closed because it was included as part of the rewrite for the upcoming v1.0 release.
The gist of it is that locations now have a state object for squireling away any extra data you need about the current request/transition (the two are analagous) being handled:
On the server, you're dealing with one request at a time, so you create a static Location with data from req.body
On the client you pass the state object (containing extracted form data) to transitionTo().
Now your transition hook is capable of receiving the same form data in both environments. If things go well, great! If things don't go well, you need a way to pass errors and re-render the form again. New state object to the rescue again! Use transition.redirect() and pass both input data and errors and you now have everything you need to render on both sides.
I'm not going into more specific detail right now because v1.0 is still in beta and v0.13.x doesn't have the necessary API to do this anyway, but I have a repository which uses the pull request above to implement this workflow with 0.13.x which you could look at in the meantime:
isomorphic-lab - the README gives an overview of how things fit together.
Here are some rough flow diagrams of the process, too:
Server POST with errors and redisplay
Client POST with errors and redisplay
I've also created a few reusable modules related to this scenario:
get-form-data gets data from a form's inputs in the format it would have been POSTed in.
react-auto-form provides <AutoForm>, which you can use instead of <form> to receive all the data from a form's inputs as an argument to its onSubmit handler
react-router-form, which is to <form> what React Router's <Link> is to <a> - it handles triggering a transition to the given action, passing method and body (form data) state - this will be updated for v1.0 soon.

Related

How do I store my Spotify access_token local so I can access it in different Javascript files

Hi Im relatively new to coding with Javascript, and Im trying to work out how I can access Spotify API's access_token within different javascript files. Im running two local server, one React and the other in node. The React server has a button that allows the user to login with Spotify's Oauth in the node server and then the access token and refresh token are redirect successfully back to the react server. The OAuth works quite well as I got it from the Spotify API guide they provide. The way I set up these files is that in my main project I have a auther-server older which includes the Spotify OAuth files and a client folder which is where i create my React app in. Here is where i got the OAuth files( https://github.com/spotify/web-api-auth-examples ). The only things changed in these files was in the app.js found in authorization_code folder where I entered my client and secret id with the correct redirect url, and I also did:
res.redirect('http://localhost:3000/#' +
querystring.stringify({
access_token: access_token,
refresh_token: refresh_token
}));
} else {
res.redirect('/#' +
querystring.stringify({
error: 'invalid_token'
}));
where I made the redirect link go to http://localhost:3000/# with the tokens which is my React app is. From there I go to my React directory and in the App.js file I follow a guide i found on youtube which helps me get what the user is now playing ( https://www.youtube.com/watch?v=prayNyuN3w0&t=1496s). Here is the code:
import React, { Component, useEffect } from "react";
import "./App.css";
import Spotify from 'spotify-web-api-js';
const spotifyWebApi = new Spotify();
class App extends Component {
constructor() {
super();
const params = this.getHashParams();
this.state ={
loggedIn: params.access_token ? true : false,
nowPlaying: {
name: 'Not Checked',
image: '',
}
}
if (params.access_token){
spotifyWebApi.setAccessToken(params.access_token)
}
}
getHashParams() {
var hashParams = {};
var e,
r = /([^&;=]+)=?([^&;]*)/g,
q = window.location.hash.substring(1);
while ((e = r.exec(q))) {
hashParams[e[1]] = decodeURIComponent(e[2]);
}
return hashParams;
}
getNowPlaying(){
spotifyWebApi.getMyCurrentPlaybackState()
.then((response) => {
this.setState({
nowPlaying:{
name: response.item.name,
image: response.item.album.images[0].url
}
})
})
}
render() {
return (
<div className="App">
<a href="http://localhost:8888">
<button>Login With Spotify</button>
</a>
<div>Now Playing: {this.state.nowPlaying.name}</div>
<div>
<img src={ this.state.nowPlaying.image } style={{width: 100}}/>
</div>
<button onClick={() => this.getNowPlaying()}>Check Now Playing</button>
<br></br>
</div>
);
}
}
export default App;
This all works nicely, but Im trying to now access the user's data( playlists) so I can later make new recommendations using Naive Bayes and KNN classifiers, but Ill tackled that after I get over this bit first. Ive looked up ways of storing the tokens and found localStorage.set(token....) to store it in the browser but I havent had success with it as I am confused on where to set this in my code. I think its also worth to note im using the spotify web api js libary, https://github.com/jmperez/spotify-web-api-js . Im guessing that i would make a similar function in the class App like the getNowPlaying() to get the playlists, but I keep getting lost. This is my first question on here, and I probably didnt do a good job of explaining my issue here, but i hope i can find some help on here. Thanks and let me know if I need to provide any more information. :)
You are probably on a good lead to do what you want with localStorage.set.
It is globally accessible. You can to the localStorage.set from wherever you retrieve the token to store it. Then you will be able to do the localStorage.get from wherever you are in your app.
Using the localStorage is a good idea because it will help you keep this token between page refresh. You might want to remove it from localStorage at some point though for security purpose if someone might have access to the machine you use.
Another way if you do not want to use the localStorage is setting it on a separate JavaScript file that you would import wherever you might want to use it.
You can even look for store principles if you want to make that data centralization a step further.

How to fetch data properly from route params?

I have one 'main' route with current data presented and another route with 'favorites'
when user click on a favorite component im sending him to the main route and need to present the new data fetched from an api and updating via redux.
So im using the code below in the main component , but what i get is that the previous data is presented for a few second and only then it changes :
async componentWillMount(){
let {cityName} = this.props.match.params
if (cityName) return await this.props.onDataLoad(cityName)
if (!dailyForecasts) await
this.props.onDataLoad(this.state.defaultCity)
}
How can i avoid this behavior ?

How to save Information of user in React after user login

I write a small app with React and back-end is PHP. I have 2 type user in database (admin and student). After user login, I save information in session Storage like that ( user: { username:'abcxyz123', role: 'admin' } ). The component render based user.role. This working good. But If I open DevTools and change user.role, my app will wrong render (because user.role is very simple text). How can I avoid this ??? My code is look like that.
class MyApp extends Component {
constructor(props) {
super(props);
this.state = {
user: (window.sessionStorage.user)
? JSON.parse(window.sessionStorage.user)
: false,
};
}
checkUserToLogin = (e) => {
if( loginSuccess ){
// return data of user in variable finalData
// finalData = { username:'abcxyz123', role: 'admin' }
window.sessionStorage.setItem('user', JSON.stringify(finalData));
this.setState({ user: JSON.parse(window.sessionStorage.user) });
}
}
render() {
const {
user
} = this.state;
return (
<div>
<form onSubmit={ this.checkUserToLogin }>
<input type="text" />
<input type="password" />
<button type="submit"> Login </button>
</form>
{/*Component will render based props user.role */}
<Component user={user} />
</div>
)
}
}
I can't change my database. The data of role always 'admin' or 'student'.
If a check is done from your backend on every authenticated action, it shouldn't be a problem.
What I think you do wrong
I think you send authenticated information to the front and let it handle if they should be shown or not. Which is really bad. Every informations sent in request can possibly be read, even if it's not render in DOM. The php backend should filter information based on database role.
The solution
Only keep a token or something that authenticates your front user in its XHR request. JWT is a really great way to do it cause it can not be mutate from the front.
Handle the render or not of admin action but continue to check it in every backend request.
If the information are changed by a malicious user, it's going to be kick from backend and you don't care if the front is broken for him.
To go further
It can be interesting too to keep this token and information in a global context. For example you can use React.Context or Redux and synchronise it with your local storage.
So you don't need to go through props driling with your user data.

Conditional redirection in Next.js

Is it possible to conditionally redirect a user to another Url based on cookie value?
I know I can inspect cookie on a server & then redirect.
But what should I do if a user came through Link. (I can't use Route.push because it's undefined on the server)
Is there any way to use Router only on the browser?
I know at least one way to do this: to create simple button and add Router push & check cookies inside onClick handler, but is it a correct way to do this?
you can check if user has accessed the page via server or client side.
and after that you can conditionally redirect with the proper tool.
getInitialProps function gets a ctx object. you can check whether its on server or client like this:
import Router from 'next/router'
export default class Browse extends Component {
static async getInitialProps (ctx) {
if (ctx && ctx.req) {
console.log('server side')
ctx.res.writeHead(302, {Location: `/`})
ctx.res.end()
} else {
console.log('client side')
Router.push(`/`)
}
...

How to force update Single Page Application (SPA) pages?

In fully server side based rendering (non Web 2.0), deploying server side code would directly update client side pages upon page reload. In contrast, in React based Single Page Application, even after React components were updated, there would be still some clients using old version of the components (they only get the new version upon browser reload, which should rarely happen) -> If the pages are fully SPA, it's possible that some clients only refresh the pages after a few hours.
What techniques should be employed to make sure the old components versions are not used anymore by any clients?
Update: the API doesn't changed, only React Component is updated with newer version.
You can have a React component make an ajax request to your server, when the application loads, to fetch "interface version". In the server API, you can maintain an incremental value for the client version. The React component can store this value on the client (cookie/local storage/etc). When it detects a change, it can invoke window.location.reload(true); which should force the browser to discard client cache and reload the SPA. Or better still, inform the end-user that a new version will be loaded and ask them if they wish to save the work and then reload etc. Depends on what you wanna do.
Similar to Steve Taylor's answer but instead of versioning API endpoints I would version the client app, in the following way.
With each HTTP request send a custom header, such as:
X-Client-Version: 1.0.0
The server would then be able to intercept such header and respond accordingly.
If the server is aware that the client's version is stale, for example if the current version is 1.1.0, respond with an HTTP status code that will be appropriately handled by the client, such as:
418 - I'm a Teapot
The client can then be programmed to react to such a response by refreshing the app with:
window.location.reload(true)
The underlying premise is that the server is aware of the latest client version.
EDIT:
A similar answer is given here.
What techniques should be employed to make sure the old components
versions are not used anymore by any clients?
today (2018), many front apps use service workers. With it, it's possible to manage your app lifecycle by several means.
Here is a first example, by using a ui notification, asking your visitors to refresh webpage in order to get latest application version.
import * as SnackBar from 'node-snackbar';
// ....
// Service Worker
// https://github.com/GoogleChrome/sw-precache/blob/master/demo/app/js/service-worker-registration.js
const offlineMsg = 'Vous êtes passé(e) en mode déconnecté.';
const onlineMsg = 'Vous êtes de nouveau connecté(e).';
const redundantMsg = 'SW : The installing service worker became redundant.';
const errorMsg = 'SW : Error during service worker registration : ';
const refreshMsg = 'Du nouveau contenu est disponible sur le site, vous pouvez y accéder en rafraichissant cette page.';
const availableMsg = 'SW : Content is now available offline.';
const close = 'Fermer';
const refresh = 'Rafraîchir';
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
function updateOnlineStatus() {
SnackBar.show({
text: navigator.onLine ? onlineMsg : offlineMsg,
backgroundColor: '#000000',
actionText: close,
});
}
window.addEventListener('online', updateOnlineStatus);
window.addEventListener('offline', updateOnlineStatus);
navigator.serviceWorker.register('sw.js').then((reg) => {
reg.onupdatefound = () => {
const installingWorker = reg.installing;
installingWorker.onstatechange = () => {
switch (installingWorker.state) {
case 'installed':
if (navigator.serviceWorker.controller) {
SnackBar.show({
text: refreshMsg,
backgroundColor: '#000000',
actionText: refresh,
onActionClick: () => { location.reload(); },
});
} else {
console.info(availableMsg);
}
break;
case 'redundant':
console.info(redundantMsg);
break;
default:
break;
}
};
};
}).catch((e) => {
console.error(errorMsg, e);
});
});
}
// ....
There's also an elegant way to check for upgrades in background and then silently upgrade app when user clicks an internal link. This method is presented on zach.codes and discussed on this thread as well.
You can send app’s version with every response from any endpoint of your API. So that when the app makes any API request you can easily check there’s a new version and you need a hard reload. If the version in the API response is newer than the one stored in localStorage, set window.updateRequired = true. And you can have the following react component that wraps react-router's Link:
import React from 'react';
import { Link, browserHistory } from 'react-router';
const CustomLink = ({ to, onClick, ...otherProps }) => (
<Link
to={to}
onClick={e => {
e.preventDefault();
if (window.updateRequired) return (window.location = to);
return browserHistory.push(to);
}}
{...otherProps}
/>
);
export default CustomLink;
And use it instead of react-router's Link throughout the app. So whenever there's an update and the user navigates to another page, there will be a hard reload and the user will get the latest version of the app.
Also you can show a popup saying: "There's an update, click [here] to enable it." if you have only one page or your users navigate very rarely. Or just reload the app without asking. It depends on you app and users.
I know this is an old thread, and service workers are probably the best answer. But I have a simple approach that appears to work:
I added a meta tag to my "index.html" file :
<meta name="version" content="0.0.3"/>
I then have a very simple php scrip in the same folder as the index.html that responds to a simple REST request. The PHP script parses the server copy of the index.html file, extracts the version number and returns it. In my SPA code, every time a new page is rendered I make an ajax call to the PHP script, extract the version from the local meta tag and compare the two. If different I trigger an alert to the user.
PHP script:
<?php
include_once('simplehtmldom_1_9/simple_html_dom.php');
header("Content-Type:application/json");
/*
blantly stolen from: https://shareurcodes.com/blog/creating%20a%20simple%20rest%20api%20in%20php
*/
if(!empty($_GET['name']))
{
$name=$_GET['name'];
$price = get_meta($name);
if(empty($price))
{
response(200,"META Not Found",NULL);
}
else
{
response(200,"META Found",$price);
}
}
else
{
response(400,"Invalid Request",NULL);
}
function response($status,$status_message,$data)
{
header("HTTP/1.1 ".$status);
$response['status']=$status;
$response['status_message']=$status_message;
$response['content']=$data;
$json_response = json_encode($response);
echo $json_response;
}
function get_meta($name)
{
$html = file_get_html('index.html');
foreach($html->find('meta') as $e){
if ( $e->name == $name){
return $e->content ;
}
}
}
Yes in server side rendering if you need to update a small part of the page also you need to reload whole page. But in SPAs you update your stuffs using ajax, hence no need to reload the page. Seeing your problem I have some assumptions:
You see one of your component got updated but other components getting data from same API didn't update. Here comes Flux Architecture. where you have your data in store and your component listen to store's changes, whenever data in your store changes all your components listening to it's change will be updated (no scene of caching).
Or
You need to control your component to be updated automatically. For that
You can request your server for data in specific intervals
Websockets can help you updating component data from server.

Categories