I have an app that needs to check with a backend API before rendering 404. The routing flow works something like this:
Request comes in to /{INCOMING_PATH}, and the application attempts to fetch and render data from api.com/pages/{INCOMING_PATH}.
If the API returns 404, then the app should return 404. If not, the data is rendered.
I'm not sold on using for this use case. {INCOMING_PATH} will be dynamic, potentially with slashes and extensions in the path. Is this possible to implement in React Router (with proper SSR behavior too)? If so, how should I proceed?
(This question was originally posted on github by another user. They were requested to post it here as it is a support request. But it doesn't seem they did. I am now stuck on exactly the same issue.)
I've solved this with the React Nested Status module.
I'm using https://github.com/erikras/react-redux-universal-hot-example so this code is geared towards that. See React Nested Status for a more generic solution.
Edits to server.js:
at the top
import NestedStatus from 'react-nested-status';
at the bottom replace:
const status = getStatusFromRoutes(routerState.routes);
if (status) {
res.status(status);
}
res.send('<!doctype html>\n' +
ReactDOM.renderToString(<Html assets={webpackIsomorphicTools.assets()} component={component} store={store}/>));
with:
const repsonse = ReactDOM.renderToString(
<Html assets={webpackIsomorphicTools.assets()} component={component} store={store}/>
);
const status = getStatusFromRoutes(routerState.routes);
if (status) {
res.status(status);
}
const nestedStatus = NestedStatus.rewind();
if (nestedStatus !== 200) {
res.status(nestedStatus);
}
res.send('<!doctype html>\n' + repsonse);
Then in what ever container/component you need to serve a 404 :
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import connectData from 'helpers/connectData';
import { fetchApiData } from 'redux/modules/foo/fetchApiData';
import { NotFound } from 'containers';
#connectData(null, (getReduxState, dispatch, state, params) => {
return dispatch(fetchApiData(params.fooId));
})
#connect(
(reduxState) => ({
fooData: reduxState.foo.data,
})
)
export default class ProductType extends Component {
static propTypes = {
fooData: PropTypes.object,
}
render() {
let content;
// ... whatever your api sends back to indicate this is a 404
if (!this.props.fooData.exists) {
content = <NotFound/>;
} else {
content = (
<div className={styles.productType}>
Normal content...
</div>
);
}
return content;
}
}
Finally replace /src/containers/NotFound/NotFound.js
import React, { Component } from 'react';
import NestedStatus from 'react-nested-status';
export default class NotFound extends Component {
render() {
return (
<NestedStatus code={404}>
<div className="container">
<h1>Error 404! Page not found.</h1>
</div>
</NestedStatus>
);
}
}
I'm not sure what kind of state implementation you are using. But, if you are using redux, then I think the simplest way is to use redux-simple-router. With it, your Routes are synchronized within your state, so you can dispatch action creators to change the router path. I would try to update satate with action creators instead of pushing the state directly from a component. The truth point must be always the state, in your case I would act as follows:
The component that requires to fetch the data will be subscribed to the "dataReducer" which is the isolated state part that this component should care about. Maybe the initial state of dataReducer is an empty array. Then, in componentWillMount you dispatch an action like: dispatch(fetchDataFromApi)) If the response code is 404, then in the action fetchDataFromApi you can dispatch another action, that is just an object like this one:
{type:SET_NOT_FOUND_ERROR}
That action will be handled by the reducer dataReducer, and will return a new state with an object (consider Immutability) that will have a property error, which will be a string with the reason, or whatever you want.
Then, in componentWillReceiveProps method, you, can check if the nextProps have or not have an error. If Error, you can render your error component, or even dispatch an action to go to the error page handled by react-router.
If no error, then you can dispatch an action (thanks to redux-simple-router) to go to the path y
Related
I want to check url
private m_Router: Router
if(this.m_Router.url == "create/xxx")
{
.....
}
I could achieve checking url and take action based on URL well with code above. But I have implemented this hard-coded.
May I take path from Router ?
I can not use private a_Router: ActivatedRoute
because it is not related with url.
The main problem is that when I am in create/xxx page, when I try to navigate create/xxx/yyy url, the component that has url create/xxx , is triggered again(ngOnInıt) so I want to check in ngOnInit() if this is a url that really belong to this page. I mean how can I read PATH variable from Router ?
constructor(private router: Router ) {
}
**Then call it's URL parameter:
**
console.log(this.router.url)
if(this.router.url==""){
}
I have two components,
1. App Component
2. Main Component
app.component.ts
ngOnInit () {
this.httpService.get('./assets/batch_json_data.json').subscribe(data => {
this.batchJson = data as string [];
}
I am able to get the JSON from a file into 'batchJson' and need to pass this to my main component for further operations.
There is no event or anything that triggers this.
I have not implemented anything yet, I am trying to read #Input, #Output etc but do not understand how it works and need to go through it some more.
I have just declared basics in the main.component.ts
import { Component, OnInit, ViewChild, Input } from '#angular/core';
import { AppComponent } from '../app.component';
export class MainComponent implements OnInit {
}
Please help me out, I am an absolute rookie in Angular and am unable to try anything because my concepts are not clear and I did browse Stack Overflow, the answers are not matching my requirements.
One solution could be to use a public BehaviorSubject in your app.component.ts.
public batchJson$ = new BehaviorSubject<JSON>(null);
ngOnInit () {
this.httpService.get('./assets/batch_json_data.json').subscribe(data => {
this.batchJson = data as string [];
this.batchJson$.next(JSON.parse(data));
}
Then in your main.component.ts
constructor(private _appComponent : AppComponent )
ngOnInit(){
this._appComponent.batchJson$.subscribe((data)=>{
if(data != null){
//set data to local variable here
}
})
}
Typically I store this kind of logic in a Service, using this in a component will definitely get you pointed in the right direction to learning this concept. Preferably your component should be responsible for interacting with the UI and rendering data, while your services handle retrieving and distributing data.
you can implement common service which does all related http operations and you can inject this service in any component u want and read the json.
Make sure you return the http.get and you subscribe to it where ever you call this method.
If you are not aware of services , you can read about creating and injecting services in angular
You can use rxjs subject to emit the data through out the app and fetch it anywhere by using subject.getValue() method.
First of all you should spare time on understanding the concept of any technology before you start working on it. Else you would be spending most of the time seeking help.
I had created demo here - https://stackblitz.com/edit/angular-lko7pa. I hope it will help you out.
How should I parse this using lifecycle methods?
{"blocks":[{
"key":"33du7",
"text":"Hello there!",
"type":"unstyled",
"depth":0,
"inlineStyleRanges":[],
"entityRanges":[],
"data":{}}],
"entityMap":{}
}
I want to render the text in my component but I don't know why it throws undefined error. How should I call it?
This is my component:
class Blog extends Component{
constructor(props){
super(props);
this.blogContent = props.blogContent;
this.blogId = props.blogId;
this.handleRemoveBlog = this.handleRemoveBlog.bind(this);
this.state = {
blog__: '',
};
}
handleRemoveBlog(blogId){
this.props.removeBlog(blogId);
}
This is my lifecycle method , I would use this.setState but first of all it's giving undefined in console.
componentWillMount(){
this.state.blog__ = JSON.parse(this.blogContent);
console.log(this.state.blog__.text); // this gives undefined
}
This is the render part..
The data is coming from Firebase.
And {this.blogcontent} gives that json string that I previously mentioned.
render(props) {
return(
<div className = "blog header">
<p>{this.blog__.text}</p>
</div>
);
}
}
Blog.proptypes = {
blogContent: Proptypes.string
}
This would mostly depend on where you are getting this object from. If it is fetched over the network then the best place to pass it is in the componentDidMount. The reason for this is that the alternative lifecyle method (componentWillMount) does not guarantee a re-render of your component since it does not wait for async actions to finish execution before passing control down to your render method. Hence componentDidMount is best because as soon as new props are received or state is changed it will trigger a re-render. However, if this object is pulled from within the application then chances are, it will work just fine even if pulled within componentWillMount. This is because that operation would be much quicker, so much that control would be passed down to the render method with the new props. This is not guaranteed especially if you want to set state in the process (setting state is also async, so control might execute the rest of the code before all the required data is received).
In short, pass this to componentDidMount and in your render function, before accessing this prop, make sure that it exists. That is, instead of
render() {
return <div>{this.props.theObject.blocks[0].key}</div>
}
rather do:
render() {
return <div>{this.props.theObject && this.props.theObject.blocks[0].key}</div>
}
This is how you would do it (assuming you are getting the file over the network using axios)
componentDidMount() {
axios.get('url/to/the/file')
.then(fileData => this.setState({
data: fileData
});
}
render() {
// return whatever you want here and setting the inner html to what the state holds
}
You should not modify the state using
this.state.blog__ = JSON.parse(this.blogContent);
The proper way to do it is using the this.setState() method:
this.setState({blog__: JSON.parse(this.blogContent)})
Then, to ensure that the component will be re-rendered, use the method shouldComponentUpdate():
shouldComponentUpdate(nextProps,nextState) {
if(nextState != this.state) {
this.forceUpdate()
}
}
Take a look at the State and Lifecycle docs.
Other point: Use componentDidMount() instead of componentWillMount(), because it will get deprecated in the future.
Atention: The setState() is an asynchronous method. So, it won't instant update your state.
Use this.setState({}) in your componentWillMount function instead assign the data to the variable. Also I recommend to use componentDidMount instead of componentWillMount because it's getting deprecated in the future.
componentDidMount(){
let text = JSON.parse( this.blogContent );
this.setState({blog__: text });
}
Edit: Only use setState in componentDidMount according to #brandNew comment
So, i'm currently studying Ionic 2 to make hybrid applications. I'm following a course on Udemy but the course's content about HTTP requests to WEB API's is obsolete(it's from the ionic 2 Beta). This is a long question but some of you's who are more experienced on the Ionic 2 framework can just skip to step 8 to save some time. Thanks a lot guys!
I'm trying to retrieve data from this URL:
https: //viacep.com.br/ws/01001000/json/.
It has a space after https:// because stackoverflow won't allot me to post more than one link.
But I'm missing something to save this data on a variable I created.
What I did to this point is:
1) I generated the provider which I called ConnectionService using the CLI ionic generator.
ionic g provider ConnectionService
2) Created a method called getCEP() inside the ConnectionService Provider, which makes an HTTP GET Request
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import 'rxjs/add/operator/map';
/*
Generated class for the ConnectionService provider.
See https://angular.io/docs/ts/latest/guide/dependency-injection.html
for more info on providers and Angular 2 DI.
*/
#Injectable()
export class ConnectionService {
constructor(public http: Http) {
console.log('Hello ConnectionService Provider');
}
getCep(): Promise<Response>{
let response: any = this.http.get("https://viacep.com.br/ws/01001000/json/");
console.log("Response: " + response);
let responsePromise: any = response.toPromise();
console.log("ResponsePromise: " + responsePromise);
return responsePromise;
}
}
P.S.: Here you can see i'm loggin in two steps of the request: The first one is the response before I turn it into a Promise, so I can return it to the page. The second one is after i cast it to a Promise using the toPromise() method.
3)In my view I have a button which has the (click) directive calling the method buscarCEP()
<ion-header>
<ion-navbar>
<ion-title>Teste</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<button (click)="buscarCep()">Request CEP</button>
</ion-content>
4.1) My TypeScript file has imported the ConnectionService Provider and named it ConnectionService.
4.2) I declared the ConnectionService inside the #Component directive under the "providers:" label
4.3) I create an instance of Connection Provider that I call conServ on the constructor's declaration. Also I created a variable called CEP, to store the data that I pull from it.
import { Component } from '#angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { ConnectionService } from '../../providers/connection-service';
/*
Generated class for the MenuTest page.
See http://ionicframework.com/docs/v2/components/#navigation for more info on
Ionic pages and navigation.
*/
#Component({
selector: 'page-menu-test',
templateUrl: 'menu-test.html',
providers: [ConnectionService]
})
export class MenuTestPage {
public CEP: any;
constructor(public navCtrl: NavController, public navParams: NavParams, public conServ: ConnectionService) {
}
6)Then I modify the method buscarCEP() so that it gets that conServe instance and calls the getCEP() method which makes an HTTP Request to the URL given above.
buscarCep(): void{
this.conServ.getCep().then(data =>{
console.log("DATA:" + data);
this.CEP = data;
}).catch(err => {
console.log("ERRO: " + err);
});
console.log("CEP: " + this.CEP);
}
PS.: As you can see, i'm logging three steps into the request: The data when the getCEP() method executes, a possible error called 'err' and by the end of it, the variable CEP that I created before and saved the data value to.
7)When I run the application and click on the button, I get the following screen with the console.logs:
Image of ionic page with snips of the Chrome console
8) As you can see, my logs are returning as follows:
8.1) The "Hello ConnectionService Provider" is from the console.log inside the provider's constructor, so the import of the provider is fine and it is being instantiated.
8.2) the "Response: [object Object]" is from the first console.log() inside the getCEP() method in the provider itself.
8.3) the "RespondePromise: [object Object]" is from the second console.log() inside the getCEP() method in the provider itself, after i casted the response to a Promise.
8.4)"CEP: undefined" comes from the console.log inside the buscarCEP() method, which is called after I click on the Request CEP Button
8.5)"DATA:Response with status: 200 OK for URL: https://viacep.com.br/ws/01001000/json/" comes from the console.log() inside the buscarCEP() method.
9) From this i'm taking that the getCEP() method is being able to connect to the URL, hence why the Response and ResponsePromise logs have an Object attached to them. Also the DATA log tells me that i recieved an OK Response from the server. My question is in regard to CEP: Undefined log. I can't seem to store that object in the variable I created.
I know that this is a long one but I wanted to lay all my cards on the board and explain everything as thoroughly as I could because i'm new to this framework.
Any help is appreciated, thank you for your time!
The Response object is stored in this.CEP. The issue is console.log(this.CEP) is called before the response from the HTTP request is returned within then.Promises are asynchronous.You can check the contents by doing console.log(this.CEP) within then.
So if you were to print the data in the html side, use safe navigation operator ?. e.g: {{CEP?.property}}.
A couple of issues with your code:
You should extract the json data from your response object. I suggest you do:
this.CEP = data.json();
If you want to print the contents of the object you can try console.log(JSON.stringify(data,null,2)).
I'm using the react-router and navigate to a component that gets an ID in the URL and has to use this ID to get data from the server with the help of an action.
At the moment I'm calling the action creator in the componentWillMount hook.
This works so far, but brings a problem.
In the render method I have to check, if myData really exists with all its attributes, before I can really render.
#connect(state => {myData: state.etc.myData})
export default class extends React.Component {
componentWillMount = () => {
this.props.dispatch(
ActionCreators.getData(this.props.params.id)
)
}
render() {
if (this.props.myData.hasNotLoaded) return <br/>
...
}
}
Is there another way to get data into the store before rendering without manual checks?
You can subscribe to router's onEnter hook and dispatch actions from where.
const store = configureStore()
const routing = (<Router>
<IndexRoute onEnter={()=>store.dispatch(myLoadDataActionCreator())}/>
</Router>)
So you can avoid setState from previous answer and don't tie up component with redux.
You should create a call back, for example:
_onChange() {
this.setState(myStore.getData());
}
Then in the following react functions do the following:
componentDidMount() {
myStore.addChangeListener(this._onChange);
},
componentWillUnmount() {
myStore.removeChangeListener(this._onChange);
}
I assume you're using the mixins for the react-router, if not, take a look at the docs for it, they have some useful functions that are worth looking at.
I don't think you will need that if logic in the render() method, react will handle that with the virtual dom management and know when to load it and the data.