Why does React fetch retrieve blank object? - django

I have a working Django REST API which returns this:
{
"id": 1,
"brand": "peugeot",
"model": "3008",
"variant": "allure"
}
I am using the following code to fetch the above data:
render() {
const { brand, model, variant } = this.props;
let url = `http://127.0.0.1:8000/api/car/${brand}/${model}/${variant}/`;
console.log(url) <== url is correct when checked in console
fetch(url)
.then(response => response.json())
.then(data => data.length === 0 ? this.setState({
data : data
}) : null ) <== I have used a condition for setState to stop fetching infintely
const { data } = this.state;
console.log(data) <== This is a blank object with no data in console
console.log(data.id) <== This is undefined in console
return (
<div>
{data.id} <== No data is shown on webpage
Car Details
</div>
);
}
No error is shown when I try to fetch the data on my webpage. What am I doing wrong?
P.S. Data can be fetched from the same API server when I have an array of objects, and I use map to loop over the data. Over here I am trying to fetch a single item so there is no array, just an object. Am I doing something wrong with the syntax?

You should never fetch or setState inside the render function.
render is called many times due to all kinds of side effects, i.e scrolling, clicking, props changing etc. This kind of code could cause all kinds of trouble.
If you need to perform the request once, call the fetch function inside componentDidMount. Also, I believe your callbacks should look something like this:
fetch(url)
.then(response => response.json())
.then(data => this.setState({ data : data }))
Taken from the docs:
componentDidMount() is invoked immediately after a component is mounted. Initialization that requires DOM nodes should go here. If you need to load data from a remote endpoint, this is a good place to instantiate the network request.

I changed the condition before 'setState' to JSON.stringify(data) !== JSON.stringify(this.state.data) and now it works.

should it be:
.then(data => data.length > 0 ? this.setState({ data }) : null )

Related

PayloadTooLargeError for Expo in React Native

What is causing the PayloadTooLargeError error? I get it sometimes and also when the payload is a few KB (as far as I can figure out).
PayloadTooLargeError: request entity too large
at readStream (/usr/local/lib/node_modules/expo-cli/node_modules/#expo/dev-server/node_modules/raw-body/index.js:155:17)
at getRawBody (/usr/local/lib/node_modules/expo-cli/node_modules/#expo/dev-server/node_modules/raw-body/index.js:108:12)
at read (/usr/local/lib/node_modules/expo-cli/node_modules/#expo/dev-server/node_modules/body-parser/lib/read.js:77:3)
at jsonParser (/usr/local/lib/node_modules/expo-cli/node_modules/#expo/dev-server/node_modules/body-parser/lib/types/json.js:135:5)
at call (/usr/local/lib/node_modules/expo-cli/node_modules/connect/index.js:239:7)
at next (/usr/local/lib/node_modules/expo-cli/node_modules/connect/index.js:183:5)
at serveStatic (/usr/local/lib/node_modules/expo-cli/node_modules/serve-static/index.js:75:16)
at call (/usr/local/lib/node_modules/expo-cli/node_modules/connect/index.js:239:7)
at next (/usr/local/lib/node_modules/expo-cli/node_modules/connect/index.js:183:5)
I found some solutions that you can set the limit to a higher value, but that's not specifically for Expo.io
There is no console.log used in the app
The error you are seeing could be caused by one of the packages you are using which uses body parser.
In body parser there is an option to limit to request body size:
limit
Controls the maximum request body size. If this is a number, then the value specifies the number of bytes; if it is a string, the value is passed to the bytes library for parsing. Defaults to '100kb'.
Taken from here.
You can see a related SO questions here and here.
I also saw this GitHub issue for Expo-Cli
I had the same problem, after a lot of trials, I figure it out. The issue is related to the way you fetch the data, you are continuously fetching the data from database, which cause this error.
The solution is to fetch the data only once,
useEffect(() => {
}, []);
Here is code example to
useEffect(() => {
const fetchUser = async () => {
try {
let user = await AsyncStorage.getItem('user_id');
let parsed = JSON.parse(user);
setUserId(parsed);
//Service to get the data from the server to render
fetch('http://myIpAddress/insaf/mobileConnection/ClientDashboard/PrivateReplyConsulationTab.php?ID=' + parsed)
//Sending the currect offset with get request
.then((response) => response.json())
.then((responseJson) => {
//Successful response from the API Call
setOffset(offset + 1);
const pri = Object.values(responseJson[0].reply_list);
setPrivateReplies(pri);
setLoading(false);
})
.catch((error) => {
console.error(error);
});
}
catch (error) {
alert(error + "Unkbown user name")
}
}
fetchUser();
}, []);
I think the storage is merging the old requests, so can you reset the async storage
AsyncStorage.clear()

Can't Programatically fetch data with Apollo Client

Most of the information out there about Apollo Client and GraphQL queries is about fetching data and immediately rendering something.
What about the common use case where I want to fetch data to, let say, update the state in which I clearly don't need to render JSX, I just want to run Javascript code.
Use the following code snippet as an example
onRefChange (formValues) {
let { project, ref } = formValues
let projectFound = find(this.state.projects, (o) => { return o.id === project.value } )
let variables = {
slug: projectFound.slug, ref: parseInt(ref)
}
console.info('variables ready', variables)
return (
<Query query={RESOLVE_REF} variables={variables}>
{ ({ data, error }) => {
console.info('data response', data)
console.info('error response', error)
return data
}}
</Query>
)
}
Apollo forces me to use the Query component just to perform a query, even when I don't want to render anything. Also those console.info never log anything, but the variables ready text does appear.
I have found that the documentation is pretty clear on using the Query component, but obscure on every option which is different. I feel I'm missing something.
I'm also concerned about how Apollo doesn't seems respect the separation of responsibilities, apparently merging both data and presentation into a single responsibility (as is clear with the Query component), which in my current understanding is quite silly, but most likely I'm fucking things up.
Any insight is appreciated.
As long as you've configured and included an ApolloProvider at the top of your component tree, you can get your query instance using either the withApollo HOC, or the ApolloConsumer:
const MyComponent = ({ client }) => {
// use it!
}
withApollo(MyComponent)
<ApolloConsumer>
{client => (
// use it!
)}
</ApolloConsumer>
You can then use any of the methods that are available to the client instance, including query and mutation, both of which return a Promise that resolves to an ApolloQueryResult object that includes data and errors. The full documentation for the client's API can be found here. Your code would then look something like:
async onRefChange (formValues) {
let { project, ref } = formValues
let projectFound = find(this.state.projects, (o) => { return o.id === project.value } )
let variables = {
slug: projectFound.slug, ref: parseInt(ref)
}
try {
const { data } = await this.props.client(RESOLVE_REF, { variables })
} catch (e) {
// Handle errors
}
}

What is wrong with my timestamp in my MWS request?

If I submit a request to MWS via the scratchpad(AmazonServices/Scratchpad),
it is successful, and I am able to view the details of the successful request. In particular, the timestamp on the request looks like this:
&Timestamp=2018-08-14T18%3A30%3A02Z
If I literally take this timestamp, as is, and try to use it in my code to make the same exact request, I get an error:
<Message>Timestamp 2018-08-14T18%3A30%3A02Z must be in ISO8601
format</Message>\n
Here is the function I am trying to place it in: (some chars changed in sensitive params)
exports.sendRequest = () => {
return agent
.post('https://mws.amazonservices.com/Products/2011-10-01')
.query({
AWSAccessKeyId: encodeURIComponent('BINAJO5TPTZ5TTRLNGQA'),
Action: encodeURIComponent('GetMatchingProductForId'),
SellerId: encodeURIComponent('H1N1R958BK8TTH'),
SignatureVersion: encodeURIComponent('2'),
Timestamp: '2018-08-14T18%3A30%3A02Z',
Version: encodeURIComponent('2011-10-01'),
Signature: encodeURIComponent(exports.generateSignature()),
SignatureMethod: encodeURIComponent('HmacSHA256'),
MarketplaceId: encodeURIComponent('ATVPDKIKX0DER'),
IdType: encodeURIComponent('UPC'),
'IdList.Id.1': encodeURIComponent('043171884536')
})
.then(res => {
console.log('here is the response');
console.log(res)
})
.catch(error => {
console.log('here is the error');
console.log(error);
})
}
What is even more strange, is that this is the path the request is being sent to:
path: '/Products/2011-10-01?
AWSAccessKeyId=BINAJO5ZPTZ5YTTPNGQA&Action=GetMatchingProductForId&SellerId=H1N1R958ET8THH&SignatureVersion=2&Timestamp=2018-08-14T18%253A30%253A02Z&Version=2011-10-01&Signature=LwZn5of9NwCAgOOB0jHAbYMeQT31M6y93QhuX0d%252BCK8%253D&SignatureMethod=HmacSHA256&MarketplaceId=ATVPDKIKX0DER&IdType=UPC&IdList.Id.1=043171884536' },
The timestamp is not the same as the one I placed in the query. Why is this happening?
Your HTTP library is already doing the url-encoding for you, so you're double-encoding things. Remove all references to encodeURIComponent() and format your timestamp normally, with : and not %3A. Observe what happens to the generated URL.
Why? URL-encoding isn't safe to do repeatedly.
: becomes %3A with one pass, but it becomes %253A with a second pass, which is wrong.

.forEach within async action creator not returning action when running unit test

I am using react/redux to generate a list of panels, each of which displays data on each list item. I set a 5 second interval that calls refreshAppList(this.props.list) action creator that forEach loops through every item in the list and makes an async call which then dispatches the refreshed list item (using redux-thunk). So basically, every 5 seconds I am refreshing the list of panels with the most up-to-date data. This works great! Unfortunately, now that I am writing unit tests for this particular async action creator I have run into an issue. .forEach does not return anything so when I call it in my unit tests I am getting undefined. Does anyone know how to override this issue or maybe i need to use a different method to refresh the entire list of panels?
Here is the action creator that is looping through the array and making an async call on each array item.
export const refreshAppList = list => (dispatch) => {
list.forEach((version, index) => {
const url = `apiEndpoint/${version.data.app_id}/${version.data.version}`;
return axios.get(url)
.then(({ data }) => {
data.uniqueId = version.uniqueId;
data.refreshId = uuidv1();
dispatch({ type: REFRESH_APP_LIST, payload: { index, data } });
})
.catch((e) => {
console.log(e);
});
});
};
Here is the error i am receiving:
1) async actions creates an action with type: REFRESH_APP_LIST:
TypeError: Cannot read property 'then' of undefined
at Context.<anonymous> (tests/asyncActions.js:140:12)
Here is where I am calling the action creator within the test (using redux-mock-store):
return store.dispatch(refreshAppList(list)).then(() => {
expect(store.getActions()).to.deep.equal(expectedActions);
});
I think it is also worth mentioning that I am using axios-mock-adapter to mock the data returned from the async call within the action creator.
One last thing: I have written unit tests for two other async action creators within the same app and both pass. The big difference is that this particular action creator is chaining together multiple async calls using a forEach loop (that is not returning anything to the test).
That doesn't work because the function that refreshAppList returns doesn't return anything. Also, .forEach doesn't return anything even though you do return axios.get. from inside. You could use .map instead and return everything inside Promise.all. Something like this
export const refreshAppList = list => (dispatch) => {
return Promise.all(list.map((version, index) => {
const url = `apiEndpoint/${version.data.app_id}/${version.data.version}`;
return axios.get(url)
.then(({ data }) => {
data.uniqueId = version.uniqueId;
data.refreshId = uuidv1();
dispatch({ type: REFRESH_APP_LIST, payload: { index, data } });
})
.catch((e) => {
console.log(e);
});
}));
};

Using Ember.set to change a value, getting error that I have to use Ember.set

In my application, I have to update a record (eventToUpdate) with the data from another object (updatedEvent). To do this, I use the following code:
editEvent (updatedEvent, eventToUpdate) {
eventToUpdate.set('name', updatedEvent.name);
eventToUpdate.set('matching', updatedEvent.matching);
eventToUpdate.set('dcfEvent', updatedEvent.dcfEvent);
eventToUpdate.save().then(() => {
toastr.success('Event updated');
}).catch((error) => {
toastr.error('There occured an error while trying to update the event');
console.log(error);
});
},
When I try to update the event, I get the following error:
Assertion Failed: You must use Ember.set() to set the `name` property (of [object Object]) to `DCF tests`."
I have also tried setting the values with Ember.set, like this:
Ember.set(eventToUpdate, 'name', updatedEvent.name);
But that gives the same result..
I use Ember.js 1.13
It seems that eventToUpdate is not an Ember Object and someone is watching this property. So use Ember.set to set values:
editEvent (updatedEvent, eventToUpdate) {
Ember.set(eventToUpdate, 'name', updatedEvent.name);
Ember.set(eventToUpdate, 'matching', updatedEvent.matching);
Ember.set(eventToUpdate, 'dcfEvent', updatedEvent.dcfEvent);
eventToUpdate.save().then(() => {
toastr.success('Event updated');
}).catch((error) => {
toastr.error('There occured an error while trying to update the event');
console.log(error);
});
},
The problem lied in the structure of my data. I was trying to edit sub events, which are part of a root event. The problem was solved by first loading the single subevent from the backend and then editing that single subevent. It looks somewhat like this:
var event = this.store.findRecord('event', subevent.id);
event.set('name', updatedEvent.name);
event.set('matching', updatedEvent.matching);
event.save().then(() => {
toastr.success('element successfully updated');
}).catch((error) => {
console.log(error);
});