How to render promise on a initial load of a React Native component - es6-promise

I'm trying to use React Native's DatePickerAndroid. But I want to show it on an initial load of an component. But the problem is that DatePickerAndroid is a promise (see docs) and I could't load it on initial render.
Thus, what I have is :
render(){
let datePicker = <DatePickerIOS date={this.state.selectedValue} mode="date" timeZoneOffsetInMinutes={this.state.timeZoneOffsetInHours * 60} onDateChange={(date) => { this.setState({selectedValue: date})}} />;
if (Platform.OS === "android"){
datePicker = this.showAndroidDatePicker()
}
return(
<View>
{datePicker}
</View>
)
}
Where showAndroidDatePicker function is as in the docs :
showAndroidDatePicker = async () => {
try {
const {action, year, month, day} = await DatePickerAndroid.open({
date: this.state.selectedValue
});
if (action !== DatePickerAndroid.dismissedAction) {
var date = new Date(year, month, day);
this.setState({selectedValue: date.toLocaleDateString()});
}
}catch ({code, message}) {
console.warn('Cannot open date picker', message);
}
};
But when I run it on android I get big red screen with an error :
Objects are not valid as a React Child (found: Object with keys ({_45,
_81, _64, _54}). ......
Any idea how to handle it?

You effectively tried to render the result of open, which according the docs tells you what the user picked. So you probably don't want to render it or have it anywhere in your render function.
Add a button to open the date picker, and when pressed call the open method. The result of it will tell you what the user picked.

Related

Taken two pages back since render function is called twice

I want to go back to the previous page when Apollo Client error.graphQLErrors has an error with a specific message from the back-end server,
Below is the snippet of my code.
const Detail = () => { const { snackbar } = useSnackbar();
const history = useHistory();
return(
<Compo query={graphQLQuery}>
{({ data, error, }) => {
if(error?.graphQLErrors[0]?.extensions?.debugMessage.includes('Specific Error')){
history.goBack();
snackbar('Specific Error');
return <></>;
}
else{
//render another component
}
}
}
</Compo>);
Issue is since the render is called twice, when the error happens, history.goBack() is executed twice and I'm taken two pages back.
I'm able to avoid this by removing <React.StrictMode> encapsulating <App> component.
Is there a better way to do this?
I'm trying to avoid removing <React.StrictMode> since it's been there since a long time.
Issue
The issue here is that you are issuing an unintentional side-effect from the render method. In React function components the entire function body is considered to be the "render" method. Move all side-effects into a useEffect hook.
Solution
Since the code is using a children function prop you'll need to abstract what the "child" is rendering into a React component that can use React hooks.
Example:
const DetailChild = ({ data, error }) => {
const history = useHistory();
const { snackbar } = useSnackbar();
const isErrorCondition = error?.graphQLErrors[0]?.extensions?.debugMessage.includes('Specific Error'
useEffect(() => {
if (isErrorCondition)) {
history.goBack();
snackbar('Specific Error');
}
}, [error]);
return isErrorCondition
? null
: (
... render another component ...
);
};
...
const Detail = () => {
return (
<Compo query={graphQLQuery}>
{({ data, error }) => <DetailChild {...{ data, error }} />}
</Compo>
);
};

I need help displaying data from the Steam API using a Flatlist in React Native

I'm trying to display game information from the Steam API in a React Native Flatlist. I'm new to React and JSX, so a lot of what I'm reading doesn't make sense.
I want the Flatlist to display a list of game titles owned by a particular account. The data returned from Steam's API call (via fetch) looks like this:
{
"response": {
"game_count": 69,
"games": [
{
"appid": 220,
"name": "Half-Life 2",
"playtime_forever": 24,
"img_icon_url": "fcfb366051782b8ebf2aa297f3b746395858cb62",
"img_logo_url": "e4ad9cf1b7dc8475c1118625daf9abd4bdcbcad0",
"has_community_visible_stats": true,
"playtime_windows_forever": 0,
"playtime_mac_forever": 0,
"playtime_linux_forever": 0
},
{
"appid": 320,
"name": "Half-Life 2: Deathmatch",
"playtime_forever": 0,
"img_icon_url": "795e85364189511f4990861b578084deef086cb1",
"img_logo_url": "6dd9f66771300f2252d411e50739a1ceae9e5b30",
"has_community_visible_stats": true,
"playtime_windows_forever": 0,
"playtime_mac_forever": 0,
"playtime_linux_forever": 0
},
and so on. Since I'm trying to display a list of games by name, the name attribute is the only one I need.
The data lists each game as an anonymous object, so I can't access the properties within each game using dot notation like I normally would. I tried using a for loop to iterate through them, but that doesn't work either. From my research, it seems like people normally use an Array.map for this kind of thing, but I'm unclear if that can be used with Objects.
Another problem I've encountered is the Flatlist keyExtractor property. I know it's supposed to be an anonymous function that returns some unique index or property about each Flatlist item, for the purpose of making the structure more efficient and to allow it to track updates to the list. However, I have no idea how to create this function myself. I think the appid field from the JSON data would be a good candidate, but I'm not sure how to get that into the keyExtractor function.
So, to put it as a question: How would I go about displaying data from a JSON object containing anonymous sub-objects in a Flatlist, and how would I populate the keyExtractor of that list with a different data entry (the appid from that list?
Below is my starting code:
import React, {Component} from 'react';
import {FlatList, Stylesheet, Text, View} from 'react-native';
export default class App extends Component {
state = {
dataset: []
};
componentWillMount() {
this.fetchData();
}
fetchData = async () => {
const response = await fetch("<API URL>");
const json = await response.json();
//const data = json.map((item) => item.games.name);
var key = 0;
const data = json[games][0][name];
this.setState({ dataset: data });
}
render() {
console.log(this.state.dataset);
return (
<View>
<FlatList
data={this.state.dataset}
keyExtractor={(x, i) => i} //I have no idea what this does, or if it makes sense here.
//Where do x and i come from? (I got this from a tutorial video
//and this was glossed over)
renderItem={({ item }) => //Where does item come from?
<Text>
{item}
</Text>
}
/>
</View>
);
}
}
Alright, it seems you're having a few minor problems with understanding how FlatList works. Let me break it down for you.
Let's start with the Steam API request. In your example, you're first declaring dataset as an empty array in your state, then trying to update it with the result of a network request which is the way to go. The problem is, when you do json['games'][0]['name'] you're accessing the first item (index 0) of the games array and getting its name property and then setting that name as your dataset. Although you forgot the quotes around property names, it won't work. What you need to do instead is something like this:
fetchAllGames = async () => {
const steamResponse = await fetch("<API URL>");
const json = await steamResponse.json();
// We get all the games back from Steam in the form of an array
this.setState({ games : json.games });
}
We're now correctly updating the array inside our state with the data from the games array.
Let's move on to the keyExtractor and renderItem functions. The keyExtractor function is used to tell React about a unique identifier for each of your list items. In this case, this would be the appid property of a game. React then uses this unique ID to differentiate between items and determine which ones need updating. This function provides you with two parameters, namely the actual item and its index. Using these, we can then do something like this:
keyExtractor = (item, index) => {
return item.appid.toString();
}
We're now returning the appid property as a string (which is the type React expects key to be).
The renderItem function is a bit different, React is providing you with a parameter which contains your item plus a lot of other properties. Since we're only interested in the actual item, we're destructuring it using brackets like so: { item }. This is a technique commonly used in JavaScript to "extract" properties from objects. It is normally used like this:
const testObj = {
name : "John",
surname : "Doe"
}
const { name, surname } = testObj;
This way, you can directly refer to name and surname as if they were independent variables. Another way of doing this would be:
const testObj = {
name : "John",
surname : "Doe"
}
const name = testObj.name;
const surname = testObj.surname;
I hope this cleared some of the questions you might've been asking yourself! Here's the complete working code below. You may notice I moved some inline functions to class members, this is just a performance optimization to prevent the functions from being recreated on every render, you can ignore that.
import React, { Component } from 'react';
import { FlatList, Text } from 'react-native';
export default class App extends Component {
state = {
games : []
};
componentDidMount() {
this.fetchAllGames();
}
fetchAllGames = async () => {
const steamResponse = await fetch("<API URL>");
const json = await steamResponse.json();
// We get all the games back from Steam in the form of an array
this.setState({ games : json.response.games });
}
keyExtractor = (item, index) => {
return item.appid.toString();
}
renderItem = ({item}) => {
return (
<Text>{item.name}</Text>
);
}
render() {
return (
<FlatList
data={this.state.games}
keyExtractor={this.keyExtractor}
renderItem={this.renderItem} />
);
}
}
EDIT #1 - As pointed out by the OP, I made a typo and corrected it. I also changed the JSON object to reflect the response property.

difficulty displaying part of a json response in react

so i created a hook to fetch the ISS API. it works fine. but i am having difficulty displaying a specific part of the json that is returned.
my react fetch hook, the useEffect part
my display code
the code works and displays the first two tags, but when i add the 3rd with location.iss_position.longitude i get an undefined error
the console.dir of the json data
i have tried many variations of location.iss_position.longitude but nothing seems to work and a few google searches were unproductive. maybe my own fault for being able to accurately describe my problem with the correct technical language.
EDIT: heres my full code for fetch and display logic. i followed a tutorial and understand about 80% of it now. still learning
export const useFetchPosition = () => {
// define states for the hook
const [location, setLocation] = useState({})
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
useEffect(() => {
// init loading and error states
setLoading(true)
setError(null)
// fetch api url
fetch(issUrl)
// return response as promise with json content
.then(res => res.json())
// return json promise, setLoading state, console log
.then(json => {
setLoading(false)
if (json) {
setLocation(json)
console.dir(json)
} else {
// this else prevents infinite loop
setLocation([])
}
})
// errors update state here
.catch(err => {
setError(err)
setLoading(false)
})
},[])
// return updated states for export to display
return { location, loading, error }
}
const Display = () => {
//call hook and hook data
const { location, loading, error } = useFetchPosition()
// loading and error
if (loading) return <div>Loading...</div>
if (error) return <div>{error}</div>
return (
<>
<h2 className="bg-gray-900 text-gray-300 text-center text-6xl">
{location.message}
</h2>
<h2 className="bg-gray-900 text-gray-300 text-center text-3xl">
response timestamp: {location.timestamp}
</h2>
<h2 className="bg-gray-900 text-gray-300 text-center text-3xl">
current latitude: {JSON.stringify(location) !== '{}' && location.iss_position.latitude}
</h2>
<h2 className="bg-gray-900 text-gray-300 text-center text-3xl">
current longitude: {JSON.stringify(location) !== '{}' && location.iss_position.longitude}
</h2>
</>
)
}
export default Display
Answer:
React isn't having difficulty displaying the JSON response; it's having trouble displaying your component before the response comes in, because you are trying to reference members of undefined objects.
Try putting JSON.stringify(location) !== '{}' && location.iss_position.latitude instead (presuming your default state, when using setState is {})
Alternatively you can define a default state in the same shape as the API's response
Explanation
This is normal Javascript behaviour.
You've assigned {} to location when you first called:
// I'm presuming you did something like this
let [location, setLocation] = setState({});
At this point, location is set to {}. You can, in any JS context, try to refer to members of an object that don't exist and you'll get undefined.
But when you do location.iss_position.longitude, you are trying to reference longitude on a member iss_position which is undefined - this will throw an error. You cannot reference members of undefined, but you can reference undefined members on a defined object.
Try running the following in your console:
let foo = {}; // Can't redefine window.location
console.log(foo); // {}
console.log(foo.iss_position); // undefined
console.log(foo.iss_position.longitude); // TypeError: location.iss_position is undefined
In fact, your console will tell you exactly that. The error your component is throwing specifically says:
location.iss_position is undefined
This is telling you that the object you are trying to reference (location.iss_position) is undefined at some point (before the API responds, for example)

ReactJS Fixed-Data-Table and Async JSON for DataListStore

I am trying to learn ReactJS with ES6 along with setting up an instance of Fixed-Data-Table. I'm using the ObjectDataExample example from the github repo, but instead of the faker() values fed to the DataListStore, I want to use a DataListStore that gets its cache from a remote JSON resource. This is how I have defined my DataListStore:
class MyDataListStore {
constructor(/* url string */ url) {
this.url = url || 'http://localhost:8080/default-json';
this._cache = [];
this.pageSize = 1;
this.size = 0;
this.getRemoteData(url);
}
getRemoteData() {
/**
* Fetch remote JSON to be used in the store.
*/
var that = this;
fetch(this.url).then(function(response) {
return response.json();
}).then(function(j) {
console.log(j);
//this.pageSize = j["pages"];
that.size = j["total"];
that._cache = j["table"];
if (that._cache) {
// do something here?
}
});
}
getObjectAt(/*number*/ index) /*?object*/ {
if (index < 0 || index > this.size){
return undefined;
}
if (this._cache[index] === undefined) {
//this._cache[index] = this.createFakeRowObjectData(index);
}
return this._cache[index];
}
getSize() {
return this.size;
}
}
module.exports = MyDataListStore;
As you can see I'm following the FakeObjectDataListStore provided with the example from fixed-data-table more or less. The JSON is fetched properly, the _cache is populated with an array of objects, and when you output getSize once getRemoteData has executed, you do get the size of the _cache. However, I haven't figured out how my fixed-data-table Table component should be updated once the data has been fetched. Currently the Table is rendered but is simple blank with no rows.
class ObjectDataExample extends React.Component {
constructor(props) {
super(props);
this.state = {
dataList: new MyDataListStore()
};
}
render() {
var {dataList} = this.state;
return <Table
rowHeight={70} rowsCount={dataList.getSize()} width={1170} height={500} headerHeight={30}>
<Column
header={<Cell>ID</Cell>}
cell={<TextCell data={dataList} col="id" />}
width={50}
fixed={true}
/>
<Column
header={<Cell>Email</Cell>}
cell={<TextCell data={dataList} col="email" />}
width={300}
fixed={true}
/>
</Table>
}
}
module.exports = ObjectDataExample;
I think the main issue is that I don't have any code meant to populate the table once MyDataListStore is populated with the data from the async call. However, I can't find any help from the examples given in the Fixed-Data-Table github repo or the docs. Any idea how to get this done? I assume I need to set up some sort of event listener, but I'm not sure where/how to do this, as I'm still new to both ReactJS and Fixed-Data-Table.
Edit: I should also add that when the page loads, I get the following error:
Uncaught TypeError: Cannot read property 'id' of undefined
once I set the initial this.size to more than 0. So of course the table doesn't have the available data when it's first loading.
Edit 2: After looking into this further, it looks like if I run the fetch in componentDidMount of my ObjectDataExample and use this.setState(); to reset the dataList object, then I get the table updated. However, this looks a little messy and I'd assume there's a better way to do this directly from my MyDataListStore object.
Thanks,
One design issue with the current implementation of MyDataListStore is that it does not provide a way to notify the caller when the data has been loaded.
One possible way you might do this is to implement some sort of factory function (in the example below, I'm pretending that one exists called MyDataListStore.of) that returns a Promise that eventually resolves the MyDataListStore instance once the data loads:
// In the ObjectData component constructor, we call the MyDataListStore
// factory function and once it resolves, we assign it to our
// state. This will cause our component to re-render.
constructor() {
MyDataListStore.of(myDataListStoreUrl).then(store => {
this.setState({ dataList: store });
});
}
Now, once the data in the data list store resolves, our template (specified in your render function) will render correctly.
The DataListStore.of function we used earlier might look something like this:
class MyDataListStore {
static of(url) {
const dataListStore = new MyDataListStore(url);
return dataListStore.getRemoteData().then(() => return dataListStore);
}
/* ... other MyDataListStore properties/methods ... */
}
And finally we need to update the getRemoteData to return a promise. This is what will allow any clients of our MyDataListStore class to be notified that the data has loaded:
getRemoteData() {
/**
* Fetch remote JSON to be used in the store.
*/
var that = this;
// Return the chained promise! This promise will resolve
// after our last callback is called.
return fetch(this.url).then(function(response) {
return response.json();
}).then(function(j) {
console.log(j);
//this.pageSize = j["pages"];
that.size = j["total"];
that._cache = j["table"];
if (that._cache) {
// do something here?
}
});
}

Parse date string to Date object when loading Angular UI Bootstrap Datepicker

I'm using Angular UI Bootstrap Datepicker:
https://angular-ui.github.io/bootstrap/#/datepicker
When I render form using data received from the server, there is problem with datetime fields. My input datepicker looks like this:
<form name="itemForm">
<input type="datetime" class="form-control" id="startedAt" name="startedAt"
ng-model="item.startedAt"
ng-click="open($event, 'startedAt')"
uib-datepicker-popup="yyyy-MM-dd"
is-open="datepickers.startedAt"
/>
</form>
My server returns response datetime as JSON string:
{
...
startedAt: "2015-05-29T02:00:00+0200"
}
When I assign response data to the model $scope.item = response;, datepicker input field is rendered correctly (correct date is selected and it's properly formatted in format I selected). The problem is that validation does not pass. I get:
itemForm.startedAt.$invalid == true
I noticed that data bound to the datepicker field should be Date object and not string (when I select new date from the datepicker, $scope.item.startedAt is a Date)
I managed to work around this issue and do this in the controller:
$scope.item = response;
$scope.item.startedAt = new Date($scope.item.startedAt);
It works this way... But I wouldn't like to manually convert string do date every time I get a response from the server. I tried to create a directive, that I can assign to the datepicker input field so it converts the ng-model for me:
.directive("asDate", function () {
return {
require: 'ngModel',
link: function (scope, element, attrs, modelCtrl) {
modelCtrl.$formatters.push(function (input) {
var transformedInput = new Date(input);
if (transformedInput != input) {
modelCtrl.$setViewValue(transformedInput);
modelCtrl.$render();
}
return transformedInput;
});
}
}
})
Well it works, because now I can see Date object, when I output model in my view: {{item.startedAt}}. However still validation fails! I suspect this is some problem with me understanding how data flows between model and the view, and how UI Bootstrap hooks into it.
Also when I change my directive from $formatters.push to $formatters.unshift, validation works OK, but datepicker does not format my datetime (insted of nicely formattet yyyy-MM-dd I see ISO string inside the input)
This broke as of Angular.UI.Bootstrap v0.13.2 (8-2-2015)
Downgrading to 0.13.1 works, which is where I'm stuck today.
Wesleycho says this was done intentionally
https://github.com/angular-ui/bootstrap/issues/4690
I'm ready for other date pickers that support strings if anyone has a suggestion
...soon after posting this I went down a non-angular path that I'm not proud of, but it works for both HTML5 type="date" and uib-datepicker-popup. I have a regular expression that determines if a string resembles one of the two serialized date formats I've seen, and then I have a recursive javascript function to traverse a json tree and replace those strings with Date(). You would call it just before you put in $scope (or viewmodel) ...
$http.get("../api/comm/" + commId
).success(function (resp) {
fixDates(resp);
vm.comm = resp;
});
(I need not check the string length, but I figured it would spare some cpu cycles by not running the regex if the string is obviously not a date)
//2015-10-01T00:00:00-04:00
//2015-11-20T18:15:56.6229516-05:00
var isDate = new RegExp("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{7})?-\\d{2}:00");
function fixDates(json) {
for (i in json)
if (typeof (json[i]) == "object")
fixDates(json[i]);
else if (typeof (json[i]) == "string" && (json[i].length == 25 || json[i].length == 33) && isDate.test(json[i]))
json[i] = new Date(json[i]);
};
As this is intentional behaviour of angular-ui-bootstrap datepicker (https://github.com/angular-ui/bootstrap/issues/4690), I ended up using Angular service/factory and moment library.
Service dateConverter can be injected globally to intercept all HTTP requestes/responses or only in desired controllers.
Here I use Restangular library to handle request to REST API, hence the response.plain() method which takes only object properties, and not Restangular methods/properties.
var Services = angular.module('app.Services', []);
Services
.factory('dateConverter', ['dateFilter', function (dateFilter) {
var dateConverter = {};
dateConverter.prepareResponse = function (response) {
for(prop in response.plain()) {
if (response.hasOwnProperty(prop)) {
if(moment(response[prop], moment.ISO_8601, true).isValid()) {
response[prop] = new Date(response[prop]);
}
}
}
return response;
};
dateConverter.prepareRequest = function (item) {
for(prop in item.plain()) {
if (item.hasOwnProperty(prop)) {
if(angular.isDate(item[prop])){
item[prop] = dateFilter(item[prop] , "yyyy-MM-ddTHH:mm:ssZ")
}
}
}
return item;
};
return dateConverter;
}])
;
you can transform String to Date in restangular transformer, something like this
RestangularConfigurer
.addElementTransformer('<RESTRESOURCENAME>', false, function (element) {
element.createDate = new Date(element.createDate);
return element;
})