I cracking my head against the wall, I'm developing a React App with wordpress as an API (wp rest API). everything is working well, I'm fetching and rendering all the post but when I go to the single post I can't fetch and render the title and content because it's said {rendered: "dwdwdf"} and I do the {post.title.rendered} as I do in the postlist component to get the title and works but in the single post doesn't.
To make it more clear here is the code:
const id = this.props.match.params.id;
let postUrl = `http://thesite.com/wp-json/wp/v2/posts/${id}`;
fetch(postUrl)
.then(data => data.json())
.then(data => {
this.setState({
item: data
})
})
when I console the this.state.item.title it shows me this:
{rendered: "dwdwdf"}
rendered: "dwdwdf"
__proto__: Object
it should be render as I do in my postlist component, like this {this.state.item.title.rendered} and done ! but not, it gave me this error:
TypeError: Cannot read property 'rendered' of undefined
I checked the api rest wp documentation and a lot of blogs and stackoverflow but I can't find anything that helps me.
if someone can guide me, thanks.
In principle, what you have shown should work. The response of that WP API endpoint does include:
"title": {
"rendered": "Your post's title"
},
This is backed up by your console.log output.
You haven't shown your default state, where your console.log output is written, or where you are trying to access the full path (i.e. {this.state.item.title.rendered}), but it sounds like you are doing at least the full path part in the render function.
Assuming so, I believe what you have is a timing issue. The render function may run as many times as it needs to (i.e. when the component updates). The first time it runs, your state does not have the title property yet, as your HTTP request is not yet complete. It's undefined, and when you try to access a child property of undefined, you get that error.
The difference with your earlier console statement is you aren't trying to access a property of undefined. You are just outputting the state's value itself (i.e., undefined). And one very tricky thing about the console is that it's not a historical record. A value that a console.log shows can change...say from 'undefined' to the value that gets set there later, title property and all. It all happens so fast that you don't see this.
It's best to keep in mind that the render() function may run over and over again, and your JSX needs to be written in such a way that it accounts for the possible states you expect. Here, you can expect that initially your state for "item" does not have all the properties that it will have later.
You could instead write something like,
{this.state.item.title ? this.state.item.title.rendered : 'Loading...'}
Or whatever else you'd like to write there (or leave it blank, etc.). So you are first checking to see if title has a truthy value (not undefined), and if so, accessing the child's property. A common pattern is to use this form:
{this.state.item.title && (<h1>{this.state.item.title.rendered}</h1>)}
Here the difference is we are relying on JavaScript's return value for &&, which will be the second item's value. If the first item is falsy, it doesn't even look at the second part, so it doesn't complain.
Or, you may try to take a step back and track a separate variable in state for your loading process. Something like dataReady, which you would set to true once you receive the HTTP request back. Then, your render code looks a bit cleaner:
{this.state.dataReady && (
<h1>{this.state.item.title.rendered}</h1>
<h2>(some other stuff</h2>
)}
For more information, I recommend you read about conditional rendering.
I'm just guessin but this may be due to your state not having the needed initial conditions to render correctly, since you are loading the info asynchronously.
For example, if your intial state is like this:
this.state = {
item: {},
...anyOtherAttributes
}
and you try to render this.state.item.title.rendered, it won't find title so it'll be undefined and then React won't be able to get the rendered property correctly.
To fix it you should initialize your posts like:
this.state = {
item: {
title: {rendered: ''}
},
...anyOtherAttributes
}
so it has kind of a placeholder to render while waiting for the aynchronous call to end and update the state.
Hope it helps.
Related
I am executing a GraphQL query which is fairly complex. Sometimes it returns undefined, but when I refresh the page it works.
I'm building a web application using ReactJS for the frontend, and GraphQL to fetch the data. The query is quite complicated and even when I comment out the entire code in the page (so no information is actually rendered to the page, just they query is executed) , it still sometimes returns undefined.
I've tried the Query in the GraphQL playground and it works fine.
If I look at my server response, the 0.chunk.js.map response time is very long.
Does anyone know what this is likely to be?
The page I am rendering executed 5 different graphQL queries at different levels in the component hierarchy. But it is always the queries in the highest level component that frequently returns undefined.
data may be undefined while the query is being fetched from the server. You can provide a default value for it so that the destructuring will work regardless:
({ data: { movie } = {}, error, loading }) => {
You can even access more nested fields using destructuring, but you need to provide a default value at each level. For example:
({ data: { movie: { title } = {} } = {}, error, loading }) => {
Note that data could end up null (as opposed to undefined) if the request returns an error and it bubbles all the way up to the root. In this case, defaults won't help. But you can still guard against nulls this way:
const movie = data && data.movie
or use something like lodash's get.
I'm working with ReactJS on a project and using KnexJS to access a database. I would like to store and use the JSON objects I retrieve, in the state of a component as something along the lines of the following.
this.setState({tutors: tutors}) //Retrieval from database after promise
and then access their properties like so
this.state.tutors[0].email
this.state.tutors[0].firstname
However, when I try this, I get the following error
TypeError: Cannot read property 'email' of undefined
I have checked the console.log as well as using JSON.stringify to determine if the objects are retrieved by the time I need them. And it appears that they are. I simply cannot access their properties from state.
Is there something I can do about this? Or do I need to retrieve them from the DB one by one?
This could be happening, because at the moment your code tries to retrieve data from state, the data is not there yet -- though it's possibly added later. This is something that happens quite often, in my experience. You should probably check that an object exists, before trying to access a property on it -- and return null or undefined in case it doesn't exist:
this.state.tutors[0] ? this.state.tutors[0].email : null
EDIT (in response to the additional code samples):
Assuming your fetch function works ok and adds the fetched data to state (I would use forEach instead of map to push the elements into the array, or just map over the fetched array and add that to the state directly, without an intermediary array/variable/push -- but I think your code should work)...
The problem has to do with the render method, since when the component renders initially the data is not yet in state, and as such you're passing an empty array to the TutorTable component, which probably in turn produces the error you see.
The data gets added to state later, but at this stage the error on the initial render has already happened.
You could solve this by rendering the TutorTable conditionally, only when the data gets added to the state:
<div className="col-7">
<h3> My Tutors </h3>
{this.state.tutors.length > 0 && <TutorsTable tutors={this.state.tutors} />}
</div>
if you console.log out this.state.tutors in the render() method you should see in the console an empty array ([]) returned on the initial render, and then an array filled with data when the component re-renders when the data is added to the state.
I hope this helps.
It appears to me you are trying to get the first element of an array this.state.tutors[0].email
is the tutors really an array,could provide the format of the data that you console logged as you stated.This probably should be in the comment section but I have less than 50 rep so i cant comment.
you should check first tutors. if you are getting tutors an array then you can access its first element.
if(this.state.tutors[0]) {
console.log("first element of tutors array", this.state.tutors[0].email)
// anything you can acccess now of this element
}
and assign first tutors as an empty array in state.
state = {
tutors: []
}
in first lifecycle you have no data in this.state.tutors[0]
for this reason first check this.state.tutors[0] is ready or not like this
if(this.state.tutors[0])
{
var data=this.state.tutors[0];
data.push('your data');
this.setState({tutors:data})
}
I know Redux solves this but I came up with an idea.
Imagine I have an app that gets some JSON on start. Based on this JSON I'm setting up the environment, so let's assume the app starts and it downloads an array of list items.
Of course as I'm not using Redux (the app itself is quite simple and Redux feels like a huge overkill here) if I want to use these list items outside of my component I have to pass them down as props and then pass them as props again as deep as I want to use them.
Why can't I do something like this:
fetch(listItems)
.then(response => response.json())
.then(json => {
window.consts = json.list;
This way I can access my list anywhere in my app and even outside of React. Is it considered an anti-pattern? Of course the list items WON'T be changed EVER, so there is no interaction or change of state.
What I usually do when I have some static (but requested via API) data is a little service that acts kind like a global but is under a regular import:
// get-timezones.js
import { get } from '../services/request'
let fetching = false
let timez = null
export default () => {
// if we already got timezones, return it
if (timez) {
return new Promise((resolve) => resolve(timez))
}
// if we already fired a request, return its promise
if (fetching) {
return fetching
}
// first run, return request promise
// and populate timezones for caching
fetching = get('timezones').then((data) => {
timez = data
return timez
})
return fetching
}
And then in the view react component:
// some-view.js
getTimezones().then((timezones) => {
this.setState({ timezones })
})
This works in a way it will always return a promise but the first time it is called it will do the request to the API and get the data. Subsequent requests will use a cached variable (kinda like a global).
Your approach may have a few issues:
If react renders before this window.consts is populated you won't
be able to access it, react won't know it should re-render.
You seem to be doing this request even when the data won't be used.
The only downside of my approach is setting state asynchronously, it may lead to errors if the component is not mounted anymore.
From the React point of view:
You can pass the list from top level via Context and you can see docs here.
Sample of using it is simple and exists in many libraries, such as Material UI components using it to inject theme across all components.
From engineering concept of everything is a trade of:
If you feel that it's gonna take so much time, and you are not going to change it ever, so keep it simple, set it to window and document it. (For your self to not forget it and letting other people know why you did this.)
If you're absolutely certain they won't ever change, I think it's quite ok to store them in a global, especially if you need to access the data outside of React. You may want to use a different name, maybe something like "appNameConfig"..
Otherwise, React has a feature called Context, which can also be used for "deep provision" - Reference
I have the following code in my render method:
render() {
return (
<div>
{this.props.spatulaReady.ready() ? <p>{this.props.spatula.name}</p> : <p>loading spatula</p>}
</div>
)
}
Which according to my understanding, checks if the subscriptionhandle is ready (data is there) and displays it. If no data is available, it should display a simple loading message. However, when I first load the page this snippet is on, it get's stuck on the loading part. On a page reload the data (usually) displays fine.
If I check the spatulaReady.ready() when the page first loads and while the display is stuck on 'loading spatula', and the data that should be there, the handle reports as ready and the data is there like it is supposed to be. If I refresh the page it all displays fine as well. The problem is, this way of checking for data and rendering if it has arrived has worked fine for me in the past. Is it because the render method is not reactive? Because handle.ready() should be reactive.
What makes it even weirder is that it sometimes DOES correctly display the data on page load, seemingly at random.
CreateContainer code:
export default createContainer(props => {
return {
user: Meteor.user(),
spatulaReady: Meteor.subscribe('spatula.byId', props.deviceId),
spatula: SpatulaCollection.findOne()
}
}, SpatulaConfig)
Publication code:
Meteor.publish('spatula.byId', function(deviceId) {
const ERROR_MESSAGE = 'Error while obtaining spatula by id'
if (!this.userId) //Check for login
throw new Meteor.Error('Subscription denied!')
const spatula = SpatulaCollection.findOne({_id: deviceId})
if(!spatula) //No spatula by this id
throw new Meteor.Error(403, ERROR_MESSAGE)
if(spatula.ownedBy != this.userId) //Spatula does not belong to this user
throw new Meteor.Error(403, ERROR_MESSAGE)
return SpatulaCollection.find({_id: deviceId})
})
I know I'm missing a piece of the puzzle here, but I've been unsuccessful at finding it. If you don't know the solution to my specific problem, pointing me in the right direction with another way of waiting for the data to arrive before displaying it is also greatly appreciated.
EDIT: After doing some trial-and-error and reading various other posts somewhat related to my project, I figured out the solution:
export default createContainer(props => {
const sHandle= Meteor.subscribe('spatula.byId', props.deviceId)
return {
user: Meteor.user(),
spatulaReady: sHandle.ready(),
spatula: SpatulaCollection.findOne()
}
}, SpatulaConfig)
It still makes no sense to me that moving the ready() call to create container fixed all my problems though.
As you figured out, moving the .ready() call to createContainer fixes the problem. This is because Meteor reactivity only works when you call a reactive data source (a reactive function), such as collection.find() or subscriptionHandle.ready() within a reactive context, such as Tracker.autorun or createContainer. Functions within the React component, including render, are not reactive contexts from Meteor's perspective.
Note that React and Meteor reactivity are two different things. React's reactivity works simply so that whenever a component's props or state change, it's render function is re-run. It does not understand anything about Meteor's reactive data sources. Since createContainer (that is re-run by Meteor reactivity when reactive data sources in it change) simply passes props to the underlying component, the component is re-rendered by React when the props given from createContainer change.
I'm running into a weird case that only seems to happen upon first loading a component on a heavily based component page (loading 30+ components).
#Component{
selector: <parent-component>
template: `<child-component [myObject]=myObject>
}
export class ParentComponent {
private myObject:DTOValue;
constructor(service:MyService){
service.getDTOValue().subscribe((dtoValue:DTOValue) => {
this.myObject = dtoValue;
});
}
}
#Component{
selector: <child-component>
template: `<div></div>
}
export class ChildComponent {
#Input set myObject(value:DTOValue) => {//do something};
constructor(){
}
}
In this code, the Parent is going to get a value to a child as an input. This value comes from a request at a later time, so when the child is first initialized, the input could be undefined. When the value does get returned from the request and is set on the variable myObject, I'd expect that the child component would receive an input event being triggered. However, due to the timing, it seems like this is not always the case, especially when I first load a page that contains a lot of files being loaded.
In the case that the child component doesn't receive the input, if I click else where on my page, it seems to now trigger the change detection and will get the input value.
The 2 possible solutions I can think of that would require some large code changes so I want to make sure I choose the right now before implement them.
Change the input to be an Subject, so that I push the input value which should ensure that a correct event is triggered(this seems like overkill).
Use the dynamic loader to load the component when the request as return with the correct value (also seems like overkill).
UPDATE:
Adding a plnker: http://plnkr.co/edit/1bUelmPFjwPDjUBDC4vb, you can see in here that the title seems to never get its data bindings applied.
Any suggestions would be appreciated.
Thanks!
If you can identify where the problem is and appropriate lifecycle hook where you could solve it, you can let Angular know using ChangeDetectorRef.
constructor(private _ref: ChangeDetectorRef)
method_where_changes_are_overlooked() {
do_something();
// tell angular to force change detection
this._ref.markForCheck();
}
I had a similar issue, only with router - it needed to do redirect when/if API server goes offline. I solved it by marking routerOnActivate() for check...
When you trigger change detection this way a "branch" of a component tree is marked for change detection, from this component to the app root. You can watch this talk by Victor Savkin about this subject...
Apologize, the issue ended up being my interaction with jQuery. When I triggered an event for a component to be loaded, inside of the jQuery code, it wouldn't trigger the life cycle. The fix was after the code was loaded to then call for a change detection.