How to combine two similar state values from hook in React - javascript

Im struggling to combine my state values into one in elegant way without if statements. The problem is following i'm making two separate calls into api and they return values like data, error and status message.
const [{ data: userData, error: userError, status: userStatus }] = useFetch(API_USER)
const [{ data: clientData, error: clientError, status: clientStatus }] = useFetch(API_client);
const mappedData = combineData(userData, userData);
Im mapping and merging data into one and passing it into component using props, component is consuming this values like this:
<Loader error={userError || clientError} status={status}>
<UserPanel data={mappedData}/>
</Loader>
But i don't know what to do with status and error from my hook, how i can calculate one value for both calls. So for example when first status is 'fetched' and the second one is still 'fetching' i can get value fetching because one call into api is still in progress. Same for errors, when one of them is true i pass error value to loader. Status can be only a string. Error string or undefined. What is the best possible way to set one single value for this situation.
I came with the solution which involves some if statements and make new state value for status and error based on both, something like this, but i didn't like this approach:
let status;
if(userStatus === 'fetching' || clientStatus === 'fetching') {
status = 'fetching'
}

Related

Apollo/GraphQL/React, How to query for data in a loop?

In one of my React components, I have an array that includes a number of IDs as Strings. I want to query each ID in the array and return an array of its associated objects within my MongoDB database. However, this only happens conditionally, so I wanted to use useLazyQuery instead of useQuery.
I tried to accomplish it via mapping, but it seems I had an issue with using useLazyQuery. Here's the issue:
const [queryId, {loading, data}] = useLazyQuery[QUERY_ID];
if (queryForIds) {
// Using just the first ID
(queryId({variables: {id: idList[0]}}));
console.log(data);
}
This results in an infinite loop of nothing being printed (maybe my resolver returns nothing but regardless I get an infinite loop that crashes my site). I am most likely misusing useLazyQuery but I'm unsure of how.
Originally my idea was to do this:
const [queryId, {loading, data}] = useLazyQuery[QUERY_ID];
if (queryForIds) {
// Using just the first ID
idList.map((id) => (queryId({variables: {id: idList[0]}}))).data);
}
But I'm also unsure if that works either. How can I resolve either issue?

Can't access array after passing it to state, but can access it before

I have an pseudo-object is inside my state. I've been able to access through a couple layers, but when I reach the array inside the object Im getting undefined errors.
UPDATE: Its something wrong with how I pass lambdaReturnObject to the state which isn't letting me access the array, tested with lambdaReturnObject.campaigns[0].campaignName and it worked.
handleSearch() {
//data to use to query backend
let campaignId = this.refs.campaignInput.value
let marketplace = this.refs.marketplaceInput.value
//using local copy of backend data, production should call backend fo this instead
let lambdaReturn = "{\"advertiser\":{\"advertiserId\":\"1\",\"enforcedBudget\":0.1},\"campaigns\":[{\"campaignID\":\"1\",\"campaignName\":\"fake\",\"createDate\":11111,\"creationDate\":1111,\"startDate\":1111,\"endDate\":1111,\"dailyBudget\":0.1,\"internal\":{\"budgetCurrencyCode\":\"USD\",\"inBudget\":true},\"enforcedBudget\":0.1,\"budgetCurrencyCode\":\"USD\",\"budgetPacingStrategy\":\"asp\",\"budgetType\":\"averageDaily\",\"status\":\"enables\",\"internalStatus\":\"enabled\"}],\"campaignID\":\"1\"}"
let lambdaReturnObject = JSON.parse(lambdaReturn)
this.setState({
apiData: lambdaReturnObject
})
}
When I try and go to the array inside, I get the following error
<h3>Campaigns :{console.log(this.state.apiData.campaigns[0].campaignName)}</h3>
Cannot read property '0' of undefined
This means I am accessing it the wrong way, but I looked at other posts (Accessing Object inside Array) and I thought that this was right. Though I am definitely wrong or else I wouldn't be writing this.
JSON.parse() is synchronous function, so set state wont be called till, JSON.parse() executes completely and returns the object.
Still You can try following
Call JSON.parse() using a try-catch block like below and see if it works. Also it is error free way of parsing your stringified objects.
try {
let lambdaReturnObject = JSON.parse(lambdaReturn)
this.setState({
apiData: lambdaReturnObject
})object
}
catch (err) {
// Do error handling here.
}
Use optional chaining, and try to access your object like this.state.apiData.campaigns?.[0].campaignName; this won't give error even if compaigns is undefined.
Refer : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
Depending on what is happening. The call to get campaigns[0] is getting resolved before the API call finishes. You can try a promise or async await to make sure the object is retrieved from the API call before you attempt to access it.
Do you happen to have a code snippet of is being used to get the data?
The error was that render() calls right as the page is booted up. At that time the object is not stored in my state so trying to call
this.state.objects.innerObject.property
would fail because I declared objects in my state, innerObject could be counted as null before the object is actually loaded in so it wouldn't throw an error. But property would throw an error because innerObject as we know, is null.
Fix by using an if statement before rendering the page to see if the object is actually loaded in. If not render just default empty JSX.

How to check if a JSON object is present in a file in JavaScript

I am building an app in react-native and have been trouble getting some trouble doing error checking. I am using redux and thunk to store a JSON file from the API that I am searching from. Sometimes, the search will lead back with a JSON file that contains an error message(most likely due to spelling error or unsure about what I am exactly searching for). I am trying to build a function in my app that will run in an componentWillMount function that first checks if the file has an error meassage and if so will send it back to the home screen to redo the search. The problem that am encountering is that I do not know what to code inorder to see what the error is or even if there is an error to begin with.
this is what the error object will look like in the JSON file and this
"error": {
"type": "NameResolutionException",
"message": "Name resolution error: Taxon 'Mallard' or 'Bat' resolve to multiple nodes"
},
This is the function that I built
componentDidMount = () => {
console.log("STATE", this.state);
if(this.props.response.articles.error == undefined){
if(this.props.response.articles.error.type === "NameResolutionException"){
Alert.alert("Please Refine Search or Specify Taxon.")
.then(()=>this.props.navigation.navigate("Home"));
}
}
};
The if statements will never hit despite the fact that the json file will the error object in it.
The expected output is that the error will be caught and then the app will go back to its the home screen but instead the app will fail because certain components will be missing.
Your code seems not correct, you are saying if error is undefined then comparing for a string value. This is contradictory.
It should be something like this, considering error is an object not an array
componentDidMount = () => {
console.log("STATE", this.state);
if(this.props.response.articles.error && this.props.response.articles.error.type){ // considering error object may or may not be present
if(this.props.response.articles.error.type === "NameResolutionException"){
Alert.alert("Please Refine Search or Specify Taxon.")
.then(()=>this.props.navigation.navigate("Home"));
}
else if (this.props.response.articles.error.type === "OtherPossibleStringException"){
}
else {
//Any unhandled error case
}
}
};

An array with objects, within an object is undefined

I have an array that contains objects, inside an object.
I can console.log the first object and the array, but when i try to access the objects within the array or use the map-function on the array i get an error that says "Can't read property of undefined".
I have thoroughly searched SO and other sites for similar problems and found some but no answers seems to work for me.
The object looks like this:
{
answers: [{…}],
createdAt: "2019-01-23T10:50:06.513Z",
nested: {kebab: "jjjj", sås: 2, sallad: "kkk"},
text: "weaxcc",
/* etc... */
}
And i can access it using: this.state.data
I want to access objects inside the answers-array like:
this.state.data.answers[0].text
or even :
this.state.data.answers.map().....
But that gives me 'Cannot read property '0' of undefined. The answers-array is not empty.
Any help is appreciated!
EDIT
This is how the objects ends up in my state.
getQuestionFromDb = () => {
axios.get(`http://localhost:3000/questions/${this.state.id}`)
.then(res => this.setState({
data: res.data
}));
};
This function is called in the ComponentDidMount()-method.
Here is my render function (the console.log is causing the error):
render() {
return (
<div className="main-content">
<h2>{this.state.data.text} </h2>
{console.log(this.state.data.answers[0].text)}
<p>Introducing <strong>{this.state.id}</strong>, a teacher who loves teaching courses about <strong>{this.state.id}</strong>!</p>
<input
type="text"
onChange={e => this.setState({ message: e.target.value })}>
</input>
<button onClick={() => {this.handleAnswerPost(this.state.message)}}>Answer</button>
</div>
);
}
}
componentDidMount is getting called when your component becomes part of the DOM but the call you do to populate your state is async due to XHR, which may take 100-300ms more to get the data in your component, so this.state.data.answers won't be available in the initial render() cycle.
since you mentioned using a loop, I suggest setting an initial state shape like
this.state = {
data: {
answers: []
}
}
your initial render won't have anything to loop but as soon as it resolves the data and sets the new state, it will render correctly.
alternatively you can
return this.state.data.answers.length ? loopItemsHere : <div>Loading..</div>
obviously, loopItemsHere can be anything you write to show the answers.
This might not be working when data doesnt contain answers[] at the very first mount for a component.
You may wanna check for your array's existance as following:
const { data } = this.state;
data.hasOwnProperty('answers') && console.log(data.answers[0]);
The reason we can't access object key in Javascript, usually it is because the variable/value is not the type of Object. Please ensure that this.state.data type is Object. You can try to console.log( typeof this.state.data ). We should expect that it is output object. If the type is string, you should parse it first with JSON.parse().
There may be a number of reasons that could cause the data object not to be shown:
The object was not in state at the time of it being called. This may be caused by an async operator not loading in the data. E.g. if you are requesting for the object from the database, chances are that at the time of making a call to retrieve the data (this.state.data), the response had not been given.
The object may not have been parsed into string. Try running console.log(typeof this.state.data). If the output is string, then you may have to parse it. If the output is undefined, then point one is valid

Parsing JSON objects array and displaying it in ReactJS

I've been facing a weird issue lately with my React App. I'm trying to parse a JSON object that contains arrays with data. The data is something like this:
{"Place":"San Francisco","Country":"USA", "Author":{"Name":"xyz", "Title":"View from the stars"}, "Year":"2018", "Places":[{"Price":"Free", "Address":"sfo"},{"Price":"$10","Address":"museum"}] }
The data contains multiple arrays like the Author example I've just shown. I have a function that fetches this data from a URL. I'm calling that function in componentDidMount. The function takes the data i.e responseJson and then stores it in an empty array that I've set called result using setState. In my state I have result as result:[]. My code for this would look something like this:
this.setState({result:responseJson})
Now, when I've been trying to access say Author Name from result I get an error. So something like this:
{this.state.result.Author.Name}
I'm doing this in a function that I'm using to display stuff. I'm calling this function in my return of my render function. I get an error stating :
TypeError:Cannot read property 'Name' of undefined. I get the same error if I try for anything that goes a level below inside. If I display {this.state.result.Place} or {this.state.result.Country} it's all good. But if I try,say {this.state.result.Author.Title} or {this.state.result.Places[0].Price} it gives me the same error.
Surprising thing is I've parsed this same object in a different component of mine and got no errors there. Could anyone please explain me why this is happening?
If I store the individual element while I setState in my fetch call function, I can display it. For example:
{result:responseJson,
AuthorName:responseJson.Author.Name
}
Then I'm able to go ahead and use it as {this.state.AuthorName}.
Please help me find a solution to this problem. Thanks in advance!
It could be that your state object is empty on the first render, and only updated with the data from the API after the request has completed (i.e. after the first render). The Name and Place properties don't throw an error, as they probably resolve to undefined.
Try putting an if block in your render method to check if the results have been loaded, and display a loading indicator if they haven't.
I'm guessing your initial state is something like this:
{ results: {} }
It's difficult to say without seeing more code.
[EDIT]: adding notes from chat
Data isn't available on first render. The sequence of events rendering this component looks something like this:
Instantiate component, the initial state is set to { results: [] }
Component is mounted, API call is triggered (note, this asynchronous, and doesn't return data yet)
Render method is called for the 1st time. This happens BEFORE the data is returned from the API request, so the state object is still {results: [] }. Any attempts to get authors at this point will throw an error as results.Authors is undefined
API request returns data, setState call updates state to { results: { name: 'test', author: [...] } }. This will trigger a re-render of the component
Render method is called for the 2nd time. Only at this point do you have data in the state object.
If this state evolves, means it is changed at componentDidMount, or after a fetch or whatever, chances are that your state is first empty, then it fills with your data.
So the reason you are getting this error, is simply that react tries to get this.state.result.Author.Name before this.state.result.Author even exists.
To get it, first test this.state.result.Author, and if indeed there's something there, then get Author.Name like this.
render(){
return(
<div>
{this.state.result.Author ? this.state.result.Author.Name : 'not ready yet'}
</div>
);
}
[EDIT] I'll answer the comment here:
It's just because they are at a higher level in the object.
this.state.result will always return something, even false if there is no result key in your state (no result key in your constructor for instance when the component mounts).
this.state.result.Country will show the same error if result is not a key of your state in your constructor. However, if result is defined in your constructor, then it will be false at first, then become the data when the new state populates.
this.state.result.Author.Name is again one level deeper...
So to avoid it, you would have to define your whole "schema" in the constructor (bad practice in my opinion). This below would throw no error when getting this.state.result.Author.Name if I'm not mistaken. It would first return false, then the value when available.
constructor(props){
super(props);
this.state={
result: {
Author: {}
}
}
}

Categories

Resources