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>
Related
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;
What I want to do, is create a HOC that has a method that can be triggered by whatever Parent Component is using that HOC to wrap.
For this HOC, I'm trying to fade out the HOC and any components inside it:
HOC:
export function fadeOutWrapper(WrappedComponent) {
return class extends Component {
constructor(props) {
super(props);
this.state = {
showElement: true,
removeElement: false,
};
}
_triggerFade = () => {
this._fadeOut(this.props.time).then(time => this._removeElement(time));
}
_fadeOut = time => {
let _this = this;
return new Promise((resolve, reject) => {
_this.setState({
showElement: false
});
setTimeout(() => {
resolve(time);
}, time);
});
};
_removeElement = time => {
let _this = this;
setTimeout(() => {
_this.setState({
removeElement: true
});
}, time + 500);
};
render() {
return this.state.removeElement ? null : (
<div
className={
this.state.showElement
? "cfd-container"
: "cfd-container cfd-fadeout"
}
>
<WrappedComponent {...this.props} />
</div>
);
}
};
}
How this component is being used in parent component:
import ComponentToBeFaded from '...';
import { fadeOutWrapper } from '...';
const WrappedComponent = fadeOutWrapper(ComponentToBeFaded);
class ParentComponent extends Component {
const...
super...
handleChildClick = () => {
// ? how to trigger the HOC _triggerFade method?
// WrappedComponent._triggerFade()
}
render() {
return (
<WrappedComponent time={1000} handleClick={this.handleChildClick} {...other props component needs} />
)
}
}
What I want to be able to do is call a method that is inside the HOC, can't seem to check for a change in props inside the HOC... only inside the HOC's render()
Need to keep writing more to meet the submission quota. Any thoughts on how to do this is appreciated. Hope your day is going well!
You don't need showElement in local state of the wrapped component because it's not controlled by that component. Pass it as props and use componentDidUpdate to start fading out.
const { Component, useState, useCallback } = React;
const Button = ({ onClick }) => (
<button onClick={onClick}>Remove</button>
);
function App() {
const [show, setShow] = useState(true);
const onClick = useCallback(() => setShow(s => !s), []);
return (
<WrappedButton
time={1000}
onClick={onClick}
showElement={show}
/>
);
}
function fadeOutWrapper(WrappedComponent) {
return class extends Component {
constructor(props) {
super(props);
this.state = {
removeElement: false,
fadeout: false,
};
}
componentDidUpdate(prevProps) {
if (
this.props.showElement !== prevProps.showElement &&
!this.props.showElement
) {
this._triggerFade();
}
}
_triggerFade = () => {
this._fadeOut(this.props.time).then(() =>
this._removeElement()
);
};
_fadeOut = time => {
this.setState({ fadeout: true });
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, time);
});
};
_removeElement = time => {
this.setState({
removeElement: true,
});
};
render() {
return this.state.removeElement ? null : (
<div>
{JSON.stringify(this.state)}
<WrappedComponent {...this.props} />
</div>
);
}
};
}
const WrappedButton = fadeOutWrapper(Button);
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Hi I am trying to change the state of a component during the render. The state should change the classname depending on the list passed to it as props. I have tried but it does not seem to work. I can pass props but not change the state.
class Square extends React.Component {
constructor(props) {
super(props);
this.state = {alive: true};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => ({
alive: !state.alive
}));
};
render() {
return <div className = { this.state.alive ? "square--green" : "square--grey" } onClick = { this.handleClick } />;
};
}
function SquareList(props) {
const oxGrid = props.oxGrid;
const listItems = [];
oxGrid.forEach((item, i) => {
if(item === 'O'){
listItems.push(<Square key= {i}/>)
}
else {
listItems.push(<Square key = {i} />)
}
});
return listItems;
};
let printer = (function () {
let print = function (oXGrid) {
return ReactDOM.render(<SquareList oxGrid ={oXGrid} />, grid);
};
return { print: print };
})();
I have made the following changes in Square and SquareList Component. You need to pass a prop item to the Square Component.
class Square extends React.PureComponent {
constructor(props) {
super(props);
const isALive = this.props.item === 'O';
this.state = {
alive: isALive
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => ({
alive: !state.alive
}));
};
componentWillReceiveProps(nextProps) {
if(this.props.item !== nextProps.item) {
this.setState({
alive: nextProps.item === '0'
});
}
}
render() {
return <div className = { this.state.alive ? "square--green" : "square--grey" } onClick = { this.handleClick } />;
};
}
function SquareList(props) {
const oxGrid = props.oxGrid;
const listItems = oxGrid.map((item, i) => {
return(
<Square item={item} key= {i}/>
)
});
return listItems;
};
let printer = (function () {
let print = function (oXGrid) {
return ReactDOM.render(<SquareList oxGrid ={oXGrid} />, grid);
};
return { print: print };
})();
class App extends Component {
render() {
return (
........
{ let tags = this.state.tags;
for (var key in tags) {
<p className="h3 text-primary">{tags[key]}</p>
}}
.........
)
}
}
I have error: Failed to compile - Unexpected token. I don't understand where I have mistake. Help me please.
replace
{
let tags = this.state.tags;
for (var key in tags) {
<p className="h3 text-primary">{tags[key]}</p>
}
}
with
{Object.keys(tags).map(key => <p key={key} className="h3 text-primary">{tags[key]}</p>)};
First of all you don't have a state in your app. You can't use this.state.something if there is not a state.
Secondly, do not use for loops in your render function like this. There are better ways to do this as suggested in the other answer. But if you really want to do this:
class App extends React.Component {
state = {
tags: {
foo: "somefoo",
bar: "somebar",
baz: "somebaz"
}
};
render() {
const { tags } = this.state;
const arr = [];
for (var key in tags) {
arr.push(<p className="h3 text-primary">{tags[key]}</p>);
}
return (
arr
);
}
}
But, render method is so crowded, let's make a little bit cleaning.
class App extends React.Component {
state = {
tags: {
foo: "somefoo",
bar: "somebar",
baz: "somebaz"
}
};
getTags = () => {
const { tags } = this.state;
const arr = [];
for (var key in tags) {
arr.push(<p className="h3 text-primary">{tags[key]}</p>);
}
return arr;
}
render() {
return (
this.getTags()
);
}
}
But there are better ways to iterate over stuff like .map or Object(keys) then .map for your example as suggested.
class App extends React.Component {
state = {
tags: {
foo: "somefoo",
bar: "somebar",
baz: "somebaz"
}
};
render() {
const { tags } = this.state;
return (
Object.keys(tags)
.map(key => <p key={key}>{tags[key]}</p>)
);
}
}
But again, you can use a separate function to get a cleaner structure for your render method.
class App extends React.Component {
state = {
tags: {
foo: "somefoo",
bar: "somebar",
baz: "somebaz"
}
};
getTags = () => {
const { tags } = this.state;
return Object.keys(tags)
.map( key => <p key={key}>{tags[key]}</p>)
}
render() {
return (
this.getTags()
);
}
}
Nice and clean! Don't forget if you need other JSX in here you have to use {} to use javascript expressions. For example what if you want a wrapper div top of your p's?
class App extends React.Component {
state = {
tags: {
foo: "somefoo",
bar: "somebar",
baz: "somebaz"
}
};
getTags = () => {
const { tags } = this.state;
return Object.keys(tags)
.map( key => <p key={key}>{tags[key]}</p>)
}
render() {
return (
<div>
{ this.getTags() }
</div>
);
}
}
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.