I am relatively new to React-JS and was wondering how I could pass my variables to my export function. I am using the jsPDF library.
At the time the Summary page is showing up, every thing is already in the database.
The Summary page creates in every round an IdeaTable component, writes it into an array and renders it bit by bit if the users click on the Next button (showNextTable()).
This component can use a JoinCode & playerID to assemble the table that was initiated by this player.
import React, { Component } from "react";
import { connect } from "react-redux";
import { Box, Button } from "grommet";
import IdeaTable from "../playerView/subPages/ideaComponents/IdeaTable";
import QuestionBox from "./QuestionBox";
import { FormUpload } from 'grommet-icons';
import jsPDF from 'jspdf';
export class Summary extends Component {
state = {
shownTable: 0
};
showSummary = () => {};
showNextTable = () => {
const { players } = this.props;
const { shownTable } = this.state;
this.setState({
shownTable: (shownTable + 1) % players.length
});
};
exportPDF = () => {
var doc = new jsPDF('p', 'pt');
doc.text(20,20, " Test string ");
doc.setFont('courier');
doc.setFontType('bold');
doc.save("generated.pdf");
};
render() {
const { topic, players } = this.props;
const { shownTable } = this.state;
const tables = [];
for (let i = 0; i < players.length; i++) {
const player = players[i];
const table = (
<Box pad={{ vertical: "large", horizontal: "medium" }}>
<IdeaTable authorID={player.id} />
</Box>
);
tables.push(table);
}
return (
<Box
style={{ wordWrap: "break-word" }}
direction="column"
gap="medium"
pad="small"
overflow={{ horizontal: "auto" }}
>
<QuestionBox question={topic} />
{tables[shownTable]}
<Button
primary
hoverIndicator="true"
style={{ width: "100%" }}
onClick={this.showNextTable}
label="Next"
/>
< br />
<Button
icon={ <FormUpload color="white"/> }
primary={true}
hoverIndicator="true"
style={{
width: "30%",
background: "red",
alignSelf: "center"
}}
onClick={this.exportPDF}
label="Export PDF"
/>
</Box>
);
}
}
const mapStateToProps = state => ({
topic: state.topicReducer.topic,
players: state.topicReducer.players
});
const mapDispatchToProps = null;
export default connect(mapStateToProps, mapDispatchToProps)(Summary);
So basically how could I include the IdeaTable to work with my pdf export?
If you want to use html module of jsPDF you'll need a reference to generated DOM node.
See Refs and the DOM on how to get those.
Alternatively, if you want to construct PDF yourself, you would use data (e.g. from state or props), not the component references.
Related side note:
On each render of the parent component you are creating new instances for all possible IdeaTable in a for loop, and all are the same, and most not used. Idiomatically, this would be better:
state = {
shownPlayer: 0
};
Instead of {tables[shownTable]} you would have:
<Box pad={{ vertical: "large", horizontal: "medium" }}>
<IdeaTable authorID={shownPlayer} ref={ideaTableRef}/>
</Box>
And you get rid of the for loop.
This way, in case you use html dom, you only have one reference to DOM to store.
In case you decide to use data to generate pdf on your own, you just use this.props.players[this.state.shownPlayer]
In case you want to generate pdf for all IdeaTables, even the ones not shown, than you can't use API that needs DOM. You can still use your players props to generate your own PDF, or you can consider something like React-Pdf
Related
Code below:
codesandbox.io/s/muddy-monad-zqwt73?file=/src/App.js
Currently, unsure why, the pages are not making it into the document and an empty Document is rendered.
I am unsure why. To me, this reads as I have created page elements, placed them in a list, and I should be able to map through and render all the pages to the Document. I am unsure why it is not working.
EDIT
As per suggestion, I changed the code to look as follows:
const styles = StyleSheet.create({
header: {
flexDirection: 'row',
textAlign: 'left',
fontSize: '30px',
fontFamily: 'SF Pro Text',
marginBottom: '25px',
marginTop: '30px',
marginLeft: '30px',
justifyContent: 'space-evenly'
}
})
class DavidPDF extends Component {
constructor(name, params) {
super(name, params);
this.name = name;
this.params = params;
this.content = [];
}
buildPages() {
console.log("building")
for (let i = 0; i < this.params.length; i += 1) {
console.log(i)
let jsxPage = this.buildPage(this.params[i])
this.content.push(jsxPage)
}
}
buildPage(pageParams) {
console.log("building indv page")
const pageName = pageParams.pageName;
const viewParams = pageParams.views;
const pageViewParams = [];
for (let i = 0; i < viewParams.length; i += 1) {
let view = this.buildView(viewParams[i]);
pageViewParams.push(view);
}
return (
<Page>
<View>{pageName}</View>
</Page>
)
}
buildView(viewParams) {
if (viewParams.viewType == "headerText") {
const text = viewParams.text;
return (
<View>
<Text style={styles.header}>
{text}
</Text>
</View>
)
}
}
render() {
console.log("rendering")
this.buildPages(this.params)
console.log(this.content)
return (
<Document>
{this.content}
</Document>
)
}
}
function Test() {
const pagesInfo = [
{
pageName: "Xtina's Page",
views: [
{
viewType: "headerText",
text: "Xtina's page"
}
]
}
]
let wtf = ["hi2", "hi1", "hi3"]
const wtfItems = wtf.map((item) => <div>{item}</div>)
return (
<div id="first">
{wtf.map((item) => <div>{item}</div>)}
<PDFViewer>
<DavidPDF name="hello" params={pagesInfo} />
</PDFViewer>
</div>
)
}
export default Test;
--- EDIT ----
Hurrah! That error was fixed. Now we have a new one - the pages will not go in.
There's a few issues, but if you can toss this into codesandbox would be happy to help. You're iterating over maps needlessly, not assigning keys to mapped elements, but the error you are seeing is most likely related to:
const pdf = new DavidPDF("hello", pagesInfo)
This is an instantiation of a class, which is an object, so the error makes sense.
Why not add it into render as so:
<DavidPdf name="hello" params={pageInfp} /> or whatever the props are?
Also, you can run buildPages on componentDidMount, and not worry about calling it from the parent.
Instead of approaching it this way, which does not play well, I have switched to generating a page with all my information of interest and adding print css so that a when a user prints (or I print to send to a user), the data is formatted nicely with break points for pages using only css and #media print. I would recommend this strategy to others and am happy to elaborate further if anyone wants!
Hi I'm trying to add an instagram feed to my website but I can not find a way to make it work. I haven't really used these tools before so I don't really understand it, but made it to point where I think I have the feed in GraphQL. Now the problem is I don't know how to display the images. Can anyone help?
Here is some code:
import React from "react"
import { GatsbyImage, getImage } from "gatsby-plugin-image"
import { useStaticQuery, graphql } from "gatsby"
const CapabilityList = () => {
const data = useStaticQuery(graphql`
query InstagramQuery {
allInstagramContent {
edges {
node {
localImage {
childImageSharp {
fixed(width: 200, height: 200) {
...GatsbyImageSharpFixed
}
}
}
}
}
}
}
`)
let arrayOfInstaImages = getImage(data, 'allInstagramContent.edges')
return (
<div style={{ maxWidth: `900px`, marginBottom: `1.45rem`, display: 'flex' }}>
{arrayOfInstaImages.map((item, i) => {
return (
<div key={i} style={{ width: "200px", height: "200px" }}>
<GatsbyImage image={item.node.localImage.childImageSharp.fixed} />
</div>)
})}
</div>
)
}
export default CapabilityList;
This doesn't work and displays error: Property 'map' does not exist on type 'IGatsbyImageData'.
So I think I need some other way to display a few images from the array.
Thanks a lot for every help!
getImage is a helper function (you don't really need it) that returns an image object given a path. Not an array:
Safely get a gatsbyImageData object. It accepts several different
sorts of objects, and is null-safe, returning undefined if the object
passed, or any intermediate children are undefined.
Simply do:
return (
<div style={{ maxWidth: `900px`, marginBottom: `1.45rem`, display: 'flex' }}>
{data.allInstagramContent.edges.node.map((item, i) => {
return (
<div key={i} style={{ width: "200px", height: "200px" }}>
<Img image={item.node.localImage.childImageSharp.fixed} />
</div>)
})}
</div>
)
Note: Img for gatsby-image
Your array of iterable data is the node so you should look and loop there (you can console.log it to help you understand the data structure).
The main problem in your code, besides the wrong loop, is that you are using a GraphQL query structure for Gatsby Image (from version 2, Img from gatsby-image) while you are using GatsbyImage component (from v3 onwards, GatsbyImage from gatsby-image-plugin). You can check for the migration details at: https://www.gatsbyjs.com/docs/reference/release-notes/image-migration-guide/
If you want to use a GatsbyImage, your code should look like:
import React from "react"
import { GatsbyImage, getImage } from "gatsby-plugin-image"
import { useStaticQuery, graphql } from "gatsby"
const CapabilityList = () => {
const data = useStaticQuery(graphql`
query InstagramQuery {
allInstagramContent {
edges {
node {
localImage {
gatsbyImageData(
width: 200
placeholder: BLURRED
formats: [AUTO, WEBP, AVIF]
)
}
}
}
}
}
}
`)
return (
<div style={{ maxWidth: `900px`, marginBottom: `1.45rem`, display: 'flex' }}>
{data.allInstagramContent.edges.node.map((item, i) => {
return (
<div key={i} style={{ width: "200px", height: "200px" }}>
<GatsbyImage image={item.node.localImage.childImageSharp.gatsbyImageData} />
</div>)
})}
</div>
)
}
export default CapabilityList;
Note: the use of Img or GatsbyImage will strictly rely on your installed packages and your gatsby-config.js structure. Check it out because you are mixing concepts.
Both components are similar but they accept different image props. While GatsbyImage accepts an image prop containing gatsbyImageData, Img accepts a fluid/fixed prop of childImageSharp (fluid or fixed) data, so the query will be slightly different depending on the component used so as it should be the configuration.
Of course, this is an approximation. Your GraphQL structure may be slightly different depending on your data structure. Check the query at the GraphiQL playground (localhost:8000/___graphql)
import axios from 'axios';
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import TextField from '#material-ui/core/TextField';
import Button from '#material-ui/core/Button';
import StarIcon from '#material-ui/icons/Star';
import List from '#material-ui/core/List';
import ListItem from '#material-ui/core/ListItem';
import Paper from '#material-ui/core/Paper';
import Tabs from '#material-ui/core/Tabs';
import Tab from '#material-ui/core/Tab';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import TwitterIcon from '#material-ui/icons/Twitter';
import CloseIcon from '#material-ui/icons/Close';
import Highlighter from 'react-highlight-words';
class TwitterBot extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleTabState = this.handleTabState.bind(this);
}
state = {
loaded: [],
searched: [],
searchedTicker: '',
actveTab: '',
addedTickers: []
};
async componentDidMount() {
//Gathering data from heroku API I built and adding tweets to loaded array state
let feed = await axios.get('https://boiling-plains-63502.herokuapp.com/');
let tweets = feed.data;
this.setState({
loaded: tweets
});
}
handleChange = (e) => {
//Watching input and changing searchedTicker string while typing
this.setState({ searchedTicker: e.target.value });
};
handleTabChange = (event, newValue) => {
//Selecting the correct tab
this.setState({ tabValue: newValue });
};
handleTabState = (e, data) => {
//This is changing searchedTicker state to the value of whichever tab is clicked
this.setState({ searchedTicker: data });
};
showAll = () => {
//Clearing searched state
this.setState({ searchedTicker: '' });
};
addTicker = () => {
// Adding ticker to saved list
if (this.state.searchedTicker.length > 0) {
this.setState((state) => {
const tickers = state.addedTickers.push(state.searchedTicker);
return {
tickers,
searchedTicker: ''
};
});
} else {
alert('Plase enter a symbol to search');
}
};
removeTicker = (e, data) => {
// Removing tab
let tickers = this.state.addedTickers;
if (tickers.indexOf(data) === 0) {
tickers.shift();
this.showAll();
console.log('zero');
} else {
tickers.splice(tickers.indexOf(data));
this.showAll();
}
};
savedTickerFilter = (f) => {
this.setState({ searchedTicker: f.target.value });
};
render() {
//Trimming searched input to all lowercase and filtering displayed post within return based on search
let loaded = this.state.loaded,
searchedTicker = this.state.searchedTicker.trim().toLowerCase();
if (searchedTicker.length > 0) {
loaded = loaded.filter(function(i) {
return i.text.toLowerCase().match(searchedTicker);
});
}
//Copying loaded state and attempting to added individual numbers of tweets to each tab
let copyOfLoaded = [ ...this.state.loaded ];
let filterCopy = copyOfLoaded.filter(function(i) {
return i.text.toLowerCase().match(searchedTicker);
});
let numOfTweets = filterCopy.length;
return (
<div className="main" style={{ marginTop: 40 + 'px' }}>
<h4>Search a stock symbol below to find relevant tweets from Stocktwitz.</h4>
<h4>You may then press Add to Favorites to create a saved tab for later reference.</h4>
<div className="main__inner">
<TextField
type="text"
value={this.state.searchedTicker}
onChange={this.handleChange}
placeholder="Search Ticker..."
id="outlined-basic"
label="Search"
variant="outlined"
/>
<Button onClick={this.addTicker} variant="contained" color="primary">
Add to favorites <StarIcon style={{ marginLeft: 10 + 'px' }} />
</Button>
</div>
{/* This will be the Filter Tabs component and that will import the list thats below the Paper component below */}{' '}
<Paper square>
<Tabs indicatorColor="primary" textColor="primary" onChange={this.handleTabChange}>
<Tab label={<div className="tabs-label">All ({loaded.length})</div>} onClick={this.showAll} />
{//Mapping through tabs that are added in TwitterBot component and passed down as props to this component
this.state.addedTickers.map((i) => {
return (
<div className="tab-container">
<Tab
label={
<div className="tabs-label">
{i}
({numOfTweets})
</div>
}
key={i}
onClick={(e) => this.handleTabState(e, i)}
/>
<CloseIcon value={i} onClick={(e) => this.removeTicker(e, i)} />
</div>
);
})}
</Tabs>
</Paper>
<List className="tweets">
{loaded.map(function(i) {
return (
<ListItem key={i.id}>
{' '}
<TwitterIcon style={{ marginRight: 10 + 'px', color: '#1da1f2' }} />
<Highlighter
highlightClassName="YourHighlightClass"
searchWords={[ searchedTicker ]}
autoEscape={true}
textToHighlight={i.text}
/>,
</ListItem>
);
})}
</List>
</div>
);
}
}
export default TwitterBot;
Above is the entire component that holds all necessary logic.
I basically want {{numOfTweets}} within the tab-label to be static to each Tab thats mapped through once created. Right now it correctly will show how many items per tab there are while searching, and if clicked on current tab, but all tabs will react. I need them to stay static after search so if clicked on another tab, the past tab will still show how many tweets there were for that searched tab. Right now it's happening just because it's referencing the global loaded state, I just need way to copy that and render each one individually. I hope I explained that clear enough. You can see what I mean on my demo here: https://5ec5b3cfc2858ad16d22bd3c--elastic-khorana-7c2b7c.netlify.app/
I understand I need to break out and componentize this more, but I know theres has to be an easy solution, somehow using a simple functional component to wrap the Tab component or simple just the number that will be displayed. (I'm using Material UI)
Thank you, anything helps, just need to wrap my head around it.
Please check the codesandbox here https://codesandbox.io/s/proud-leftpad-j0rgd
I have added an object instead of a string for Addedtickers so that the count can be tracked and remains constant throughout. You can further optimize this , if you want to search again within each individual tab, but you get the gist.
Please let me know if this works for you
Consider the following React Native code:
import React from 'react';
import {Text, View, StyleSheet, Button} from 'react-native';
export default class Target extends React.Component {
constructor(props){
super(props);
this.state ={ isLoading: true};
this.hitData = this.props.hitData;
}
componentDidMount(){
for(let i of Object.keys(this.hitData)){
if(this.hitData[i] === 0){
this.hitData[i] = '---'
}
}
this.setState({
prop1: this.hitData['prop1'],
prop2: this.hitData['prop2'],
prop3: this.hitData['prop3'],
prop4: this.hitData['prop4'],
prop5: this.hitData['prop5'],
isLoading: false
});
this.onPress = this.onPress.bind(this);
}
componentDidUpdate(prevProps) {
// console.log(prevProps)
if(this.props.hitData !== prevProps.hitData){
console.log("Component "+ this.state.mac + " got new props!");
this.hitData = this.props.hitData;
this.setState((state, props) =>{
let newState = {};
for(let i of Object.keys(props.hitData))
{
if(state[i] !== props.hitData[i])
{
console.log(i + " is different!");
newState[i] = props.hitData[i]
}
}
return newState
});
}
}
onPress(txt) {
console.log(txt);
}
render(){
return(
<View style={styles.container}>
<View style={{flex: 1, flexDirection: 'row'}}>
<View style={{borderWidth: 2.5, borderColor: '#00FF00',width: '50%'}}>
<Text style={{fontSize: 19, fontWeight: 'bold'}}>{this.state.prop1}</Text>
<Text style={{fontSize: 16}} numberOfLines={1}>{this.state.prop2}</Text>
</View>
<View>
<Text>{"Prop3: " + this.state.prop3}</Text>
<Text>{"Prop4: " + this.state.prop4}</Text>
<Text>{"Prop5: " + this.state.prop4}</Text>
</View>
</View>
<Button key={this.hitData[0]} title={"BUTTON"} onPress={() => this.onPress(this.hitData[0])}/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
borderRadius: 4,
borderWidth: 2.5,
height: 100,
marginBottom: 5,
overflow: 'hidden'
}
});
This is the code for a React Native component. The component takes in information received from an EventSource in the parent element. The aim is to update the fields containing Prop[3-5]based on data from future events.
In the parent component I have a property per item on state and am defining each element like:
const targetList = this.state.filterKeys.map((item) =>
<Target key={item} hitData={this.state[item]['lastHit']}/>
In my Event handler I can then do:
this.setState({
[id]: hitIntermediate
})
to send new props to each child component.
In my child component I do see the new props arriving, but after the first update the child stops updating. It does not matter how many events I send, after the first update has been received no further updates are displayed.
The strange part is if I query this.state in the child component, I do see that the state reflects the data from the new event, but the display does not change.
Am I completely misunderstanding something here. Essentially what I am wanting to do is to do the equivalent of setting .innerHTML on the <Text> tags containing the various pieces of data, but all my updates seem to get ignored after the first.
I was able to resolve this on my own.
As it turns out, the way I was getting events into my component was silly.
I changed it so instead of maintaining a hugely complex state out in the main component, I how just pass in the EventSource and define an eventhandler inside each child component.
Now I am seeing updates in close to real-time. I would still like to improve re-render performance a little bit, but overall the issue I was having is resolved.
Now a quick bonus question: why is it I seem to see (slightly) faster performance if I have the react-devtools running?
I am passing an event handler showSpinner() from parent component. This method displays the activity Indicator in my app, the method when called from the parent class, works. But when I pass it down to a child component and then call it from the child as this.props.showSpinner(), I am getting the error
TypeError: undefined is not an object
(evaluating 'Object.keys(this.state.data)')
I am also not able to console.log the method at the child's props
Please note, I have already bound the function at the parent.
Here is a part of my code.
This is the parent component.
import React from 'react';
import { View, Button, Alert, Image, ScrollView, BackHandler, TouchableOpacity,Text, ActivityIndicator } from 'react-native';
import ProductListingItem from '../ProductCategories/ProductListingItemCategories.js';
import PusherColumnCategories from '../ProductCategories/PusherColumnCategories.js';
import NavRightButton from '../NavButton/NavRightButton.js';
import ActivitySpinner from '../ActivitySpinner.js';
const TAG = 'PRODUCTCATEGORIESPAGE';
export default class ProductCategoriesPage extends React.Component {
constructor(props) {
super(props);
/*this._getResponseFromApi = this._getResponseFromApi.bind(this);*/
this._onPressGoToCart=this._onPressGoToCart.bind(this);
if(props){
/*console.log(TAG,'constructor()','props available');*/
console.log(TAG,'constructor()','props JSON stringified = '+JSON.stringify(props));
/*this.setState({dataMain : (props.navigation.state.params.categories)});*/
}
this.state = {
dataMain: props.navigation.state.params.categories,
showIndicator: false,
};
console.log(TAG,'constructor','this.state.dataMain = '+this.state.dataMain );
}
static navigationOptions = ({navigation}) => {
return{
title: 'Categories',
headerLeft: null,
headerStyle: {
backgroundColor: '#EE162C',
},
/*headerBackTitleStyle: {
color: 'white',
},*/
headerTintColor: 'white',
headerRight: <NavRightButton navigation= {navigation}/>,
gesturesEnabled:false,
};
};
_onPressGoToCart(){
console.log(TAG,'_onPressGoToCart');
console.log(TAG,'_onPressGoToCart','navigation props ='+JSON.stringify(this.props));
const { navigate } = this.props.navigation;
navigate('CartPage');
}
componentWillReceiveProps(newProps){
console.log(TAG+'componentWillReceiveProps');
if(newProps){
console.log(TAG,'componentWillReceiveProps()','props available');
console.log(TAG,'componentWillReceiveProps()','props = '+newProps.navigation.state.params.categories);
}
}
_OnAlert(title,message){
console.log(TAG,'_onAlert');
Alert.alert(
title,
message,
[
{text:'done',onPress: () => { }}
]
);
}
componentDidMount () {
console.log(TAG,'componentDidMount');
/*this._getResponseFromApi();*/
BackHandler.addEventListener('hardwareBackPress',() => {return true});
}
componentWillMount () {
console.log(TAG,'componentWillMount');
}
_showSpinner(){
console.log(TAG,'_showSpinner');
this.setState({
showIndicator:true,
});
}
_hideSpinner(){
console.log(TAG,'_hideSpinner');
this.setState({
showIndicator:false,
});
}
render(){
console.log(TAG,'render');
console.log(TAG,'render','dataMain = '+this.state.dataMain[0].id);
// console.log(TAG,'render','showSpinner = '+JSON.stringify(this.showSpinner()));
// var tempshowspinner = this.showSpinner.bind(this);
// console.log(TAG,'render','tempshowspinner = '+JSON.stringify(tempshowspinner));
return(
<View
style={{
flex:1,
}}>
<ScrollView style = {{flex:1,
backgroundColor:'#F2F2F2',
}}>
<View style = {{
flex:1,
flexDirection:'column',
}}>
<PusherColumnCategories style = {{
flex:1,
}}
data = {this.state.dataMain}
navigate = {this.props.navigation}
showSpinner = {this._showSpinner}
hideSpinner = {this._hideSpinner}/>
</View>
</ScrollView>
<ActivitySpinner showIndicator={this.state.showIndicator}/>
</View>
);
}
}
This is the corresponding child component.
import React from 'react';
import {View, Component, Button} from 'react-native';
import ProductListingItem from './ProductListingItemCategories';
const TAG = "PUSHERCOLUMNCATEGORIES";
export default class PusherColumnCategories extends React.Component {
constructor(props){
super(props);
if(props){
console.log(TAG,'props ='+JSON.stringify(props));
/*console.log(TAG,'props data length = '+Object.keys(props.dataMain).length);*/
console.log(TAG,'Props = '+ JSON.stringify(props.data));
console.log(TAG,'Navigation Props = '+JSON.stringify(props.navigate));
}
this.state = {
data: props.data,
propsAvailable: false,
navigate: props.navigation,
};
};
componentDidMount(){
console.log(TAG,'componentDidMount');
}
componentWillReceiveProps(newProps){
console.log(TAG,'componentWillReceiveProps',newProps.data);
this.setState({
/*data: JSON.parse(JSON.stringify(newProps.data)),*/
data: (newProps.dataMain),
}, function() {
console.log(TAG,'componentWillReceiveProps','this.setState()','data = '+(Object.keys(this.state.data)));
});
}
componentDidUpdate(){
console.log(TAG,'componentDidUpdate');
}
render(){
console.log(TAG,'render()');
if(this.state.data){
console.log(TAG,'render()','state not empty');
console.log(TAG,'render()','data product_code = '+this.state.data[1].product_code);
return(
<View style = {{
flex:1,
flexDirection: 'column',
}}>
<Button
style = {{
flex:1,
}}
title = 'presshere'
onClick = {this.props.showSpinner}
/>
<RenderColumn style = {{
flex:1,
}}
data = {this.state.data}
navigate = {this.props.navigate}
showSpinner = {this.props.showSpinner}
hideSpinner = {this.props.hideSpinner}/>
</View>
);
} else {
console.log(TAG,'render()','state empty');
return(
<View style = {{
flex:1,
flexDirection: 'column',
}}/>
);
}
};
}
EDIT: I actually found the real problem. The binding stuff is good to know, but it's not the answer to the actual question. The problem is newProps.dataMain. newProps does not have a dataMain key on it, it's actually data as you can see below
<PusherColumnCategories style = {{
flex:1,
}}
data = {this.state.dataMain} // the key is `data`, not `dataMain`
navigate = {this.props.navigation}
showSpinner = {this._showSpinner}
hideSpinner = {this._hideSpinner}/>
So in this piece of code in componentWillReceiveProps
this.setState({
/*data: JSON.parse(JSON.stringify(newProps.data)),*/
data: newProps.data /* not newProps.dataMain */, // newProps.dataMain is not an actual key, so it will set `data` to undefined
}, function() {
console.log(TAG,'componentWillReceiveProps','this.setState()','data = '+(Object.keys(this.state.data))); // if you call `Object.keys` on undefined, you get the error that you posted
});
When you do myFunction.bind(obj), you create a new function that wraps your existing function and remembers the object obj you pass in, and whenever you call that new function, it calls your original function with this set to obj.
In your _showSpinner an _hideSpinner functions, you make use of this.setState, so it's important that this is set to your parent component so that you update the state of parent component, and you're trying to make sure of that, but you're also binding unnecessarily at a lot of places, like in the constructor of ProductCategoriesPage, which is unnecessary. And you also want to remove bind(this) from the following
tempshowspinner in the render function of PusherColumnCategories. this.props.showSpinner already updates the parent component since it's bound to it. So it's redundant binding it here again, which gives the wrong impression that you're binding it again to the child component, which is not the case.
The bind(this) in the Button component right below. You don't want to bind over here for the same reason