Infinite-loop in Redux and one more error - reactjs

I have a table (10X10) which renders random values. If I push the arrow (up and down) in header, the column must be changed to increasing or reducing column. After that all rows must be changed.
My code:
App.jsx
class App extends React.Component {
constructor() {
super();
const dataCompleted = generateNewData(10, dataColumnNames.columnNames.length,
dataColumnNames.columnNames);
this.store = Redux.createStore(
appReducers,
{
table: {
header: dataColumnNames.columnNames,
data: dataCompleted,
footer: generateNewFooter(dataCompleted)
},
sorting: {
sortType: null,
columnId: null,
test: null
},
showDetails: {
rowId: null
}
});
}
render() {
return <Provider store={this.store}>
<div className="container">
<Table />
</div>
</Provider>
}
}
export default App;
Table.jsx
class TableBlock extends React.Component {
constructor() {
super();
}
componentDidUpdate(){
if (this.props.sorting.sortType === 1) {
let newData1 = (oldData) => {
let newData = [];
for (let i = 0; i < oldData.length; i++) {
newData[i] = oldData[i].sort(function(a, b) {
return a + b;
});
}
return newData;
}
this.props.dispatchNewData(newData1(this.props.data));
} else {
let newData1 = (oldData) => {
let newData = [];
for (let i = 0; i < oldData.length; i++) {
newData[i] = oldData[i].sort(function(a, b) {
return a - b;
});
}
return newData;
}
this.props.dispatchNewData(newData1(this.props.data));
}
}
renderBody() {
let result = this.props.table.data.map(function (dataItem, index) {
return <Body content={dataItem} key={index} />
});
return result;
}
render() {
return <table className="table">
<Header />
<tbody>{this.renderBody()}</tbody>
<Footer />
</table>
}
}
const mapStateToProps = function(state = {}) {
return {
table: state.table,
data: state.table.data,
sorting: state.sorting,
columnId: state.sorting.columnId,
showDetails: state.showDetails
}
}
const mapDispatchToProps = function(dispatch) {
return {
dispatchNewData: function(data) {
dispatch(table(data));
}
}
}
const Table = connect(mapStateToProps, mapDispatchToProps)(TableBlock);
export default Table;
Reducer - table.js
import { TABLE } from '../constants/table';
export function table(state = {}, action) {
switch (action.type) {
case TABLE:
return {
state: state,
data: action.data
};
default:
return state
}
}
Action - table.js
import { TABLE } from '../constants/table’;
export function table(data) {
return {
type: TABLE,
data
}
}
My errors:
props.table.data - isn’t changing
if I push 3 or more time the button - appearing infinity loop
after the first pushing - appearing error:
TypeError: undefined is not an object (evaluating 'this.props.content.map')
I think object props is changing. It is not good.

Related

What's wrong with my method call I try to learn React and must pass component and Props to child

I learn Reactjs and javascript and wanted to call this withFetching Component but don't understand how to set the arguments correctly. I understand overall logic but still learning the details
Here is the switch where I call the withFetching
render() {
const theFile = encodeURI(`./images/${fileData}`);
switch (mediaType) {
case 'xlsx': {
const newProps = { ...this.props, responseType: 'arraybuffer' };
return (
<div className="pg-viewer-wrapper">
<div className="pg-viewer" id="pg-viewer">
<{withFetching(XlsxViewer, newProps, fileType="xlsx", filePath={theFile} )}/>
</div>
</div>
);
}
.........
I try like this also:(making WithFetching camel-case even it's a function)
return (
<div className="pg-viewer-wrapper">
<div className="pg-viewer" id="pg-viewer">
<WithFetching XlsxViewer={XlsxViewer} newProps={newProps} />
</div>
</div>
);
But the WithFetching constructor never firers!
I try like this:
case 'xlsx': {
const newProps = { ...this.props, responseType: 'arraybuffer', fileType: 'xlsx', filePath: { theFile } };
// return withFetching(XlsxViewer, newProps);
return (
<div className="pg-viewer-wrapper">
<div className="pg-viewer" id="pg-viewer">
{WithFetching(XlsxViewer, newProps)};
</div>
</div>
);
}
But still the WithFetching constructor never firers!
Error: (yea I know the way I use brackets are my mistake it's hard to learn)
And this is the withFetching that is in its own file called fetch-wrapper.jsx. The WrappedComponent argument is the above XlsxViewer that is the final "On-screen" Component!
import React, { Component } from 'react';
import Error from './error';
import Loading from './loading';
function withFetching(WrappedComponent, props) {
return class FetchComponent extends Component {
constructor(props) {
// eslint-disable-line no-shadow
super(props);
this.state = {};
this.xhr = this.createRequest(props.filePath);
}
componentDidMount() {
try {
this.fetch();
} catch (e) {
if (this.props.onError) {
this.props.onError(e);
}
this.setState({ error: 'fetch error' });
}
}
componentWillUnmount() {
this.abort();
}
createRequest(path) {
let xhr = new XMLHttpRequest();
if ('withCredentials' in xhr) {
// XHR for Chrome/Firefox/Opera/Safari.
xhr.open('GET', path, true);
// } else if (typeof XDomainRequest !== 'undefined') {
// // XDomainRequest for IE.
// xhr = new XDomainRequest();
// xhr.open('GET', path);
} else {
// CORS not supported.
xhr = null;
return null;
}
if (props.responseType) {
xhr.responseType = props.responseType;
}
xhr.onload = () => {
if (xhr.status >= 400) {
this.setState({ error: `fetch error with status ${xhr.status}` });
return;
}
const resp = props.responseType ? xhr.response : xhr.responseText;
this.setState({ data: resp });
};
return xhr;
}
fetch() {
this.xhr.send();
}
abort() {
if (this.xhr) {
this.xhr.abort();
}
}
render() {
if (!this.xhr) {
return <h1>CORS not supported..</h1>;
}
if (this.state.error) {
return <Error {...this.props} error={this.state.error} />;
}
if (this.state.data) {
return <WrappedComponent data={this.state.data} {...this.props} />;
}
return <Loading />;
}
};
}
export default withFetching;
And this the final XlxsViewer Component that will be visible.
Thanks to Copyright (c) 2017 PlanGrid, Inc.
import React, { Component } from 'react';
import XLSX from 'xlsx';
import CsvViewer from './csv-viewer';
class XlxsViewer extends Component {
constructor(props) {
super(props);
this.state = this.parse();
}
parse() {
const dataArr = new Uint8Array(this.props.data);
const arr = [];
for (let i = 0; i !== dataArr.length; i += 1) {
arr.push(String.fromCharCode(dataArr[i]));
}
const workbook = XLSX.read(arr.join(''), { type: 'binary' });
const names = Object.keys(workbook.Sheets);
const sheets = names.map(name => XLSX.utils.sheet_to_csv(workbook.Sheets[name]));
return { sheets, names, curSheetIndex: 0 };
}
renderSheetNames(names) {
const sheets = names.map((name, index) => (
<input
key={name}
type="button"
value={name}
onClick={() => {
this.setState({ curSheetIndex: index });
}}
/>
));
return <div className="sheet-names">{sheets}</div>;
}
renderSheetData(sheet) {
const csvProps = Object.assign({}, this.props, { data: sheet });
return <CsvViewer {...csvProps} />;
}
render() {
const { sheets, names, curSheetIndex } = this.state;
return (
<div className="spreadsheet-viewer">
{this.renderSheetNames(names)}
{this.renderSheetData(sheets[curSheetIndex || 0])}
</div>
);
}
}
export default XlxsViewer;

Uncaught TypeError: this.state.people.map is not a function in react js while using the map function

import React, { Component } from "react";
import cardsDataset from "./cardsDataset";
class EvalCards extends Component {
constructor(props) {
super(props);
this.state = {
card: null,
people: [],
};
}
componentDidMount() {
this.loadCards();
}
loadCards = () => {
for (var i = 0; i < cardsDataset.length; i++) {
this.setState({
people: this.state.people.push(cardsDataset[i].cards),
});
}
console.log("poker cards " + this.state.people);
};
render() {
{
this.state.people.map((person, s) => {
return <div key={s}>{person.cards}</div>;
});
}
return <div>Helo poker</div>;
}
}
export default EvalCards;
im getting Uncaught TypeError: this.state.people.map is not a function when i run it but not sure what is wrong here any help would be great as i dont see the issue here
When you are using this.state.people.push() the returned value is the new number of elements in the array, not the new array itself with the new element. So probably the property people changes to be an integer instead of an array, and then you can't use map on integer.
You need to create temporary copy of the array and then set it to people in setState.
Option 1:
loadCards = () => {
for (var i = 0; i < cardsDataset.length; i++) {
let newArr = this.state.people;
newArr.push(cardsDataset[i].cards);
this.setState({
people: newArr,
});
}
console.log("poker cards " + this.state.people);
};
Option 2:
loadCards = () => {
for (var i = 0; i < cardsDataset.length; i++) {
this.setState({
people: [...this.state.people , cardsDataset[i].cards],
});
}
console.log("poker cards " + this.state.people);
};
import React, { Component } from "react";
import cardsDataset from "./cardsDataset";
class EvalCards extends Component {
constructor(props) {
super(props);
this.state = {
card: null,
people: [],
};
}
componentDidMount() {
this.loadCards();
}
loadCards = () => {
for (var i = 0; i < cardsDataset.length; i++) {
this.setState({
people: [...this.state.people , cardsDataset[i].cards],
}
console.log("poker cards " + this.state.people);
};
render() {
return (
<div>
{
this.state.people.map((person, s) => {
return <div key={s}>{person.cards}</div>;
});
}
<div>Helo poker</div>
</div>
)
}
export default EvalCards;
##you can try like that is suggested to writing ##

Assigning state to props from redux does not work

import React, { Component } from 'react';
import DisplayTable from './Table.js';
class App extends Component {
constructor(props) {
super(props);
this.state = {
menuItems: this.props.menu_items,
searchString: '',
displayItems: this.props.menu_items
}
this.search = this.search.bind(this);
this.handleChange = this.handleChange.bind(this);
}
componentWillMount() {
this.props.get_menu_items_api(false);
}
componentWillReceiveProps(nextProps) {
this.setState({ menuItems: nextProps.menu_items })
}
handleChange(e, isEnter) {
const searchData = () => {
let tempMenuProductDetails = this.props.menu_items;
const filterArray = tempMenuProductDetails.reduce((result, category) => {
if (category.categoryName.toLowerCase()
.indexOf(this.state.searchString.toLowerCase()) > -1) {
result.push(category);
}
if (category.productList && category.productList.length > 0) {
category.productList = category.productList.reduce((productListResult,
productList) => {
if (!!productList.productName &&
productList.productName.toLowerCase()
.indexOf(this.state.searchString.toLowerCase()) > -1)
{
productListResult.push(productList);
}
return productListResult;
}, []);
}
return result;
}, []);
this.setState({
displayItems: filterArray
}, function () {
console.log(this.state.displayItems);
})
console.log(filterArray);
}
if (!isEnter) {
this.setState({
searchString: e.target.value
});
} else {
searchData();
}
}
search(e) {
if (e.keyCode == 13) {
this.handleChange(e, true);
}
this.handleChange(e, false);
}
render() {
console.log(this.state.displayItems);
console.log(this.props.menu_items);
console.log(this.state.menuItems);
return (
<DisplayTable dataProp={this.state.displayItems} editFuncProp=
{this.props.edit_menu_items_api} /> )
}
}
export default App;
I have this search function in this file that does not update the value of props coming from the container of redux. Now when I pass {this.state.displayItems} in menu ,it does not display the data.
But when I pass {this.props.menu_items} it displays the data and I am not able to modify this.props.menu_items on the basis of search.
I have tried this code . what should i do?
The problem seems to be that, initially this.props.menu_items is an empty array and only after some API call the value is updated and you get the returned array on the second render, thus if you use it like
<DisplayTable dataProp={this.props.menu_items} editFuncProp=
{this.props.edit_menu_items_api} />
it works. Now that you use
<DisplayTable dataProp={this.state.displayItems} editFuncProp=
{this.props.edit_menu_items_api} />
and displayItems is only initialized in the constructor which is only executed once at the time, component is mounted and hence nothing is getting displayed.
The solution seems to be that you update the displayItems state in componentWillReceiveProps and call the search function again with the current search string so that you search results are getting updated.
Code:
import React, { Component } from 'react';
import DisplayTable from './Table.js';
class App extends Component {
constructor(props) {
super(props);
this.state = {
menuItems: this.props.menu_items,
searchString: '',
displayItems: this.props.menu_items
}
this.search = this.search.bind(this);
this.handleChange = this.handleChange.bind(this);
}
componentWillMount() {
this.props.get_menu_items_api(false);
}
componentWillReceiveProps(nextProps) {
this.setState({ menuItems: nextProps.menu_items, displayItems: nextProps.menu_items })
this.handleChange(null, true);
}
handleChange(e, isEnter) {
const searchData = () => {
let tempMenuProductDetails = this.props.menu_items;
const filterArray = tempMenuProductDetails.reduce((result, category) => {
if (category.categoryName.toLowerCase()
.indexOf(this.state.searchString.toLowerCase()) > -1) {
result.push(category);
}
if (category.productList && category.productList.length > 0) {
category.productList = category.productList.reduce((productListResult,
productList) => {
if (!!productList.productName &&
productList.productName.toLowerCase()
.indexOf(this.state.searchString.toLowerCase()) > -1)
{
productListResult.push(productList);
}
return productListResult;
}, []);
}
return result;
}, []);
this.setState({
displayItems: filterArray
}, function () {
console.log(this.state.displayItems);
})
console.log(filterArray);
}
if (!isEnter) {
this.setState({
searchString: e.target.value
});
} else {
searchData();
}
}
search(e) {
if (e.keyCode == 13) {
this.handleChange(e, true);
}
this.handleChange(e, false);
}
render() {
console.log(this.state.displayItems);
console.log(this.props.menu_items);
console.log(this.state.menuItems);
return (
<DisplayTable dataProp={this.state.displayItems} editFuncProp=
{this.props.edit_menu_items_api} /> )
}
}
export default App;

setState in React results in Uncaught ReferenceError

I have an very simple piece of React code as im trying to understand the concepts.
In the code below if I comment out the setState function I can see that selectNumber runs when I expect it too. However when I try and change the state's numbersBurnt value I get an error.
class Game extends React.Component {
state = {
numbersBurnt: [1]
};
selectNumber = (clickedNumber) => {
console.log('This was run');
this.setState((prevState, props) => {
numbersBurnt = [1];
});
};
render() {
return /* Something */;
}
}
Error: Uncaught ReferenceError: numbersBurnt is not defined
UPDATE: Here is the full code:
function Stars(props) {
const starsRandNo = Math.ceil(Math.random()*9);
let stars = [];
for (let i=0; i < starsRandNo; i++) {
stars.push(<span key={i} className="star">{i+1}</span>)
}
return <div>{stars}</div>;
}
function StarsBtn(props) {
const starsBtnClick = () => {
alert('was clicked');
}
return <button onClick={starsBtnClick}>Change no of stars</button>
}
function NumberOptions(props) {
const no = 9;
let numbers = [];
const burnTest = (i) => {
if (props.numbersBurnt.indexOf(i) >= 0) {
return "number-options number-options--burnt";
} else {
return "number-options";
}
}
for (let i=0; i < no; i++) {
numbers.push(<span className={burnTest(i)}
onClick={() => props.selectNumber(i+1)} >{i+1}</span>)
}
return <div>{ numbers }</div>;
}
class Game extends React.Component {
state = {
numbersBurnt: [1]
};
selectNumber = (clickedNumber) => {
console.log(this.state.numbersBurnt);
this.setState((prevState, props) => {
numbersBurnt: [2]
}, () => { console.log(this.state.numbersBurnt)});
console.log(this.state.numbersBurnt);
};
render() {
return <div>
<Stars />
<br />
<StarsBtn />
<br />
<br />
<NumberOptions numbersBurnt={this.state.numbersBurnt}
selectNumber={this.selectNumber} />
</div>;
}
}
ReactDOM.render(
<Game />,
mountNode
);
You try it.
React state is specify in the constructor function.
class Game extends React.Component {
constructor(props){
super(props);
this.state = {
numbersBurnt: [1]
}
}
selectNumber = (clickedNumber) => {
console.log('This was run');
this.setState((prevState, props) => {
numbersBurnt: [1]
});
};
render() {
return /* Something */;
}
}
more simple.
class Game extends React.Component {
constructor(props){
super(props);
this.state = {
numbersBurnt: [1]
}
}
selectNumber = (clickedNumber) => {
console.log('This was run');
this.setState({
numbersBurnt: [1]
});
};
render() {
return /* Something */;
}
}
You can omit prevState, props.
In your setState , you should be using a : and not a =. Also remove the ; and the prevState and props in your setState as you are not making use of them
class Game extends React.Component {
state = {
numbersBurnt: [1]
};
selectNumber = (clickedNumber) => {
console.log('This was run');
this.setState({
numbersBurnt: [1]
}, () => { console.log(this.state.numbersBurnt)});
};
render() {
return /* Something */;
}
}
function Stars(props) {
const starsRandNo = Math.ceil(Math.random()*9);
let stars = [];
for (let i=0; i < starsRandNo; i++) {
stars.push(<span key={i} className="star">{i+1}</span>)
}
return <div>{stars}</div>;
}
function StarsBtn(props) {
const starsBtnClick = () => {
alert('was clicked');
}
return <button onClick={starsBtnClick}>Change no of stars</button>
}
function NumberOptions(props) {
const no = 9;
let numbers = [];
const burnTest = (i) => {
if (props.numbersBurnt.indexOf(i) >= 0) {
return "number-options number-options--burnt";
} else {
return "number-options";
}
}
for (let i=0; i < no; i++) {
numbers.push(<span className={burnTest(i)}
onClick={() => props.selectNumber(i+1)} >{i+1}</span>)
}
return <div>{ numbers }</div>;
}
class Game extends React.Component {
state = {
numbersBurnt: [1]
};
selectNumber = (clickedNumber) => {
this.setState({
numbersBurnt: [2]
}, () => { console.log(this.state.numbersBurnt)});
};
render() {
return <div>
<Stars />
<br />
<StarsBtn />
<br />
<br />
<NumberOptions numbersBurnt={this.state.numbersBurnt}
selectNumber={this.selectNumber} />
</div>;
}
}
ReactDOM.render(
<Game />,
document.getElementById('app')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

Why does my data disappear when a subscription goes through?

Why am I getting undefined for data when the subscription goes through?
I am trying to figure out where the undefined for my props.data.allLocations is coming from. Any help would be appreciated.
//Higher order component
export const LocationList = graphql(
gql`
query ($site: ID!) {
allLocations(
filter: {
site: {
id:$site
}
}
) {
id
name
}
}
`,
{
options: (ownProps)=>({
variables: {
site: ownProps.site
},
}),
//props were injected from the JSX element
props: props => {
return {
data: props.data,
subscribeToData: params => {
return props.data.subscribeToMore({
document:
gql`
subscription newLocations {
Location {
mutation
node {
id
name
}
}
}
`,
variables: {
//Empty for now
//site: props.site,
},
updateQuery: (previousState, {subscriptionData}) => {
if (!subscriptionData.data) {
return previousState;
}
var newArray =[subscriptionData.data.Location.node].concat(previousState.allLocations)
var newState = {
...previousState,
allLocations: [
{
...subscriptionData.data.Location.node
},
...previousState.allLocations
],
};
return newState
}
})
},
}
},
onError: (err) => console.error(err)
} )(EntityList)
//List Component
class EntityList extends Component {
componentWillReceiveProps(newProps) {
if (!newProps.data.loading) {
console.log(newProps)
if (this.subscription && this.props.hasOwnProperty('subscribeToData')) {
if (newProps.data.allLocations !== this.props.data.allLocations) {
console.log("Resubscribe")
// if the todos have changed, we need to unsubscribe before resubscribing
this.subscription()
} else {
console.log('else')
// we already have an active subscription with the right params
return
}
}
this.subscription = this.props.subscribeToData(newProps)
}}
render () {
if (this.props.data.loading) {
return (<div>Loading</div>)
}
var Entities = [];
for(var key in this.props.data) {
if(this.props.data[key] instanceof Array){
Entities = Entities.concat(this.props.data[key])
//console.log(this.props.data[key])
}
}
return (
<div className='w-100 flex justify-center bg-transparent db' >
<div className='w-100 db' >
{Entities.map((entity) =>
(
<EntityListView
user={this.props.user}
key={entity.id}
entityId={entity.id}
name={entity.name}
profilePic={(entity.profilePic)?entity.profilePic.uuid : this.props.defaultPic.uuid}
clickFunc={this.props.clickFunc}
/>
))}
</div>
</div>
)
}
}
Thanks to all who looked into this.
I got it to work by splitting up my queries as suggested by #nburk.
I was able to keep it modular by creating my own high order component.
If anyone is interested:
const Subscriber = (document, config) => {
return (WrappedComponent) => {
return class extends Component {
constructor(props) {
super(props)
this.state={}
this.subscription = null
}
componentWillReceiveProps(nextProps) {
console.log(nextProps)
if (!this.subscription && !nextProps.data.loading) {
console.log("Sucess")
let { subscribeToMore } = this.props.data
this.subscription = [subscribeToMore(
{
document: document,
updateQuery: (previousResult, { subscriptionData }) => {
console.log(subscriptionData)
var newResult = [subscriptionData.data[config.modelName].node].concat(previousResult[config.propName])
console.log(newResult)
return {
//"allItems"
[config.propName]: newResult,
}
},
onError: (err) => console.error(err),
}
)]
this.setState({});
}
}
render () {
if (this.props.data.loading) {
return (<div>Loading</div>)
}
return (
<WrappedComponent {...this.props} />
)
}
}
}
}
export default Subscriber
/*prop config = {
propName: "string", //allItems // from query
//so I do not have to parse the gql query (would have to recieve from parent)
modelName: "string" //Item
// from subscribe //so I do not have to parse the gql subscribe
}
*/
I now wrap my component like this:
export const LocationList = graphql(gql`...,
{options: (ownProps)=>({
variables: {
site: ownProps.site
}})})(
Subscriber(gql`...,
propName:"allLocations",
modelName:"Location",
)(EntityList))
Thanks

Resources