How should I parse this json object in react with lifecycle method? - json

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

Related

Angular 9 - Cannot find a differ supporting object 'getData()

I am getting this error trying to bind my control to its data. Here is some relevant code.
Template.
<tree-control [nodes]="getData"></tree-control>
Component.
public getData(): Observable<Array<any>> {
const assets: any = this.service.get('url', headers);
return assets;
}
Anything I have found so far is not helping. Any idea what's wrong with my code?
Thanks
First of all, you assign a function (getData) to the nodes property. I assume you want to assign the data from getData to it instead.
Secondly, the call to this.service.get is probably not being executed. Reason for that is that you do not subscribe to, what I assume, is a http-call that returns an Observable.
To fix this, you can do the following:
export class Foo {
nodeData: Observable<any>;
constructor(
private readonly service: YourService,
) {
this.nodeData = this._getData();
}
private _getData() {
return this.service.get(...);
}
}
Inside your template you can then subscribe and unsubscribe to the data automatically by using the async pipe.
<tree-control [nodes]="nodeData | async"></tree-control>
For all that to work I assume your service.get method returns an Observable.

Consuming IMDB api results bad json

I have a simple program that consumes IMDB api, I'm getting the result, but it was shown as error because the result is not a structured json.
MovieService.ts
export class MovieService {
constructor(private http:HttpClient) { }
getMovie(movie:string){
return this.http.get(this.generateURL(movie));
}
private generateURL(movie:string){
return "https://v2.sg.media-imdb.com/suggests/titles/"+movie.charAt(0)+"/"+movie+".json?callback=imdb$"+movie;
}
}
addmovie.component.ts
private _filterMovies(value: string) {
this.movieService.getMovie(value).subscribe(
movies => {
console.log(movies);
return movies;
}
);
}
ngOnInit() {
this.addMovieForm.get('movie').valueChanges.subscribe(val => {
this._filterMovies(val)
});
}
I'm getting error like
the response is of bad json. How can I format the json upon receiving? How to solve this? Any leads would be helpful.
The result is not JSON, but rather JSONP. It is essentially returning you a script that is trying to execute the callback method specified.
Instead of http.get() you should call http.jsonp(url, "imbdIgnoresThisParam").
However, according to this answer, the callback query string parameter is ignored by IMDB. The answer suggests dynamically creating the expected callback function, whose name contains the title for which you are searching. In that callback you could do a few different things.
Use the closure to call / set something in your MovieService. This will result in your call to the API throwing an error, as the Angular framework's callback will not be called as expect. You could ignore the error.
Try to call the expected Angular callback, ng_jsonp_callback_<idx>. This will prevent the API call from throwing, but it may not be reliable. The callback name is dynamic and increments with each jsonp() call. You could try to track the number of jsonp() calls in your app. And of course, the framework may change and break this solution. Concurrent calls to getMovie() may break, as the next one may step on the previous callback on the window. Use with caution!
In typescript, your getMovie() function and related helpers might look like so:
private imdbData: any = null;
private jsonpIdx = 0;
private setImdb(json: any) {
this.imdbData = json;
// or do whatever you need with this
}
getMovie(movie:string) {
// dynamically create the callback on the window.
let funcName = `imdb$${movie}`;
window[funcName] = (json: any) => {
// use the closure
this.setImdbData(json);
// or try to call the callback that Angular is expecting.
window[`ng_jsonp_callback_${this.jsonpIdx++}`](json);
}
// go get your data!
let url = this.generateURL(movie)
return this.http.jsonp(url, "ignored").subscribe((json) => {
// this happens if you successfully trigger the angular callback
console.log(json);
}, (err) => {
// this happens if the angular callback isn't called
console.log(this.imdbData); // set in closure!
});
}
Edit for Angular 4
For Angular 4, it looks like you will need to import the JsonpModule along with the HttpModule. Then, you'd inject jsonp just like you'd inject http into your service. The call to IMDB becomes this.jsop.request(url).subscribe(...) and your dynamic callback name needs to change, too.
window[funcName] = (json: any) => {
// or try to call the callback that Angular is expecting.
window["__ng_jsonp__"][`__req${this.jsonpIdx++}`]["finished"](json);
}
I don't have an Angular 5 or 6 project immediately set up, so hard to say if there are any differences with the callback in those versions.
Sort of a hack, but hope it helps!

Setting State Issue

I seem to be struggling with what should be a simple issue regarding setting State. I'm learning React so I did this across several steps. Initially I setup my components so they are setting fields using props (e.g. this.propsoverview[0].ProfileImg) which was handed down State from Overview Pane, which was initially set using a this.SetState call in componentWillMount where data was pulled from a static file.
Next I worked on adding a function to pull the data in a more dynamic way (i.e. getPatient function). I'm calling this function from the componentWillMount and I am able to do a console.log to output the JSON as a string (using JSON.stringify). So I know the JSON is being returned.
Where I'm struggling is setting the State in componnetWillMount using the returned JSON from my getPatient call. I get the error Uncaught (in prmise) Type Error: Cannot read property 'setState' of undefined on the 4th line of my code (i.e. this.setState...)
Below is my code...
componentWillMount() {
getPatient().then(function(result) {
//console.log(JSON.stringify(result));
this.setState({PATIENT: result})
})
function getPatient() {
const urlGetPatient = 'url_that_gets_patient_here';
return fetch(urlGetPatient).then(function(response) {
return response.json();
}).then(function(json) {
return json;
});
}
render() {
return (
<App>
…
<OverviewPane overview={this.state.PATIENT} />
…
</App>
}
class OverviewPane extends React.Component {
constructor(props) {
super(props);
autoBind(this);
}
render () {
return (
<table>
<tbody>
<tr>
<td><Image src={this.props.overview[0].ProfileImg}/></td>
</tr>
</tbody>
</table>
);
}
}
Any help would really be appreciated.
You're getting cannot call setState of undefined because you're using a regular function as a callback instead of an arrow function (() => {}). When using a regular function, the this parameter (context) is set to the calling function, and not what you might think (the so-called lexical this).
Change your .then callback to an arrow function, and you should be good:
componentWillMount() {
getPatient().then((result) => {
this.setState({PATIENT: result});
})
}
The this in your code is bound within the promise, you need to bind this in order to get reacts this scope.
getPatient().then(function(result) {
//console.log(JSON.stringify(result));
this.setState({PATIENT: result})
}.bind(this))

Dynamic path segment OR 404

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

Where to load the server data

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.