Dynamically generating React elements not assignind properties - reactjs

I want to use my ViewModel Component to dynamically create elements and assign properties to them. The code I have is rendering, but not what I expect. It's rendering the text in a div instead of creating my custom Debug element.
Here is Debug:
import React, { Component } from 'react';
class Debug extends Component {
render() {
return (<h3>
{this.props.o}
</h3>)
}
}
export default Debug;
I want props.o to be a shorthand for object assigned to an element, assigned in ViewModel:
Here is my ViewModel:
import React, { Component } from 'react';
class ViewModel extends Component {
loadData() {
this.setState({});
}
componentWillMount() {
this.loadData();
if (!this.state) { return; }
let stateKeys = Object.keys(this.state);
console.log(stateKeys);
if (stateKeys.length <= 0) { return; }
let elems = {};
console.log("My statekeys" + stateKeys);
for (let key of stateKeys) {
let stateValue = this.state[key];
console.log("My state value: " + stateValue);
let tagName = stateValue[0];
let values = stateValue[1];
elems[key] = values.map((item, i) => {
return React.createElement(tagName, {o: item, key: key + "" + i});
});
}
}
render() {
let keys = Object.keys(this.state);
let output = [];
for (let k of keys) {
let group = this.state[k];
for (let elem of group) {
output.push(elem);
}
}
return (<div>{output}</div>)
}
}
And using with:
class Fight extends ViewModel {
loadData() {
console.log("Loading data with recent");
this.setState({
recent: [Debug, ["Lightning Blast for 155, Critical!", "Enemy Fire Punches for 70."]]
});
}
export default ViewModel;
HTML output:
<div><div>Lightning Blast for 155, Critical!Enemy Fire Punches for 70.</div></div>
No h3. The CreateElement seems to be putting the text into the body when I want it to go into the props.

Here is the working code for dynamically generating tags in React:
import React, { Component } from 'react';
class Debug extends Component {
render() {
return (<h3>
Hey{this.props['o']}
</h3>)
}
}
export default Debug;
class Fight extends ViewModel {
loadData() {
console.log("Loading data with recent");
let myState = {
"recent": [Debug, ["LBlast", "FPunch."]]
};
this.setState(myState);
}
ViewModel
import React, { Component } from 'react';
class ViewModel extends Component {
loadData() {
this.setState({});
}
componentWillMount() {
this.loadData();
}
render() {
if (!this.state) {
return (<div></div>)
}
if (!this.state) { return; }
let stateKeys = Object.keys(this.state);
console.log(stateKeys);
if (stateKeys.length <= 0) { return; }
let elems = {};
console.log("My statekeys" + stateKeys);
for (let key of stateKeys) {
let stateValue = this.state[key];
console.log("My state value: " + stateValue);
const TagName = stateValue[0];
console.log("What is tagname?" + TagName);
let values = stateValue[1];
elems[key] = values.map((item, i) => {
console.log("My item is" + item);
return <TagName o={item} key={key + "" + i}/>;
});
}
let keys = Object.keys(elems);
let output = [];
for (let k of keys) {
let group = elems[k];
for (let elem of group) {
output.push(elem);
}
}
console.log(output);
return <div>{output}</div>
}
}
export default ViewModel;

Related

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 ##

'This' is undefined when click event method is launched

I am very confused, because when I fill this.state.seats by values, it correctly render all components in DOM. But when I click on that component (button), it returns back to App.js and shows me:
Uncaught TypeError: Cannot read property 'state' of undefined
even though, the components from this state property are displayed in DOM!
Please, does anyone know what happens?
App.js
import React, { Component } from "react";
import "./App.css";
import Seats from "./Seats";
class App extends Component {
state = {
seats: this.initializeSeats()
};
initializeSeats() {
let seats = [];
let count = 5;
for (let a = 0; a < count; a++) {
for (let b = 0; b < count; b++) {
seats.push({ key: '' + a + b, reserved: false });
}
}
seats.find(s => s.key === '00').reserved = true;
return seats;
}
onClickSeat(e) {
const seats = [...this.state.seats];
let seat = seats.find(s => s.key === e.target.value);
seat.reserved = !seat.reserved;
console.log(seat.reserved);
this.setState({ seats: seats });
}
render() {
return (
<div>
<h3>Kinosál</h3>
<Seats
seats={this.state.seats}
onClickSeat={this.onClickSeat}
/>
</div>
);
}
}
export default App;
Seats.jsx
import React, { Component } from "react";
import Seat from "./Seat";
class Seats extends Component {
render() {
const result = [];
for (let seat of this.props.seats) {
if (!seat.reserved) {
result.push({ key: seat.key, reserved: seat.reserved });
}
}
return (
<div>
{result.map(seat => (
<Seat
key={seat.key}
onClick={this.props.onClickSeat}
seat={seat}
/>
))}
</div>
);
}
}
export default Seats;
Seat.jsx
import React, { Component } from "react";
import uuid from 'uuid';
class Seat extends Component {
render() {
const { seat, onClick } = this.props;
return (
<div>
<button onClick={onClick} key={uuid.v4()} value={seat.key}>{seat.key}</button>
</div>
);
}
}
export default Seat;
take a look at https://reactjs.org/docs/handling-events.html
You have to be careful about the meaning of this in JSX callbacks. In JavaScript, class methods are not bound by default. If you forget to bind this.handleClick and pass it to onClick, this will be undefined when the function is actually called.
You have to bind onClickSeat to the App class instance of this you can use the class arrow syntax below to do so.
onClickSeat = (e) => {
const seats = [...this.state.seats];
let seat = seats.find(s => s.key === e.target.value);
seat.reserved = !seat.reserved;
console.log(seat.reserved);
this.setState({ seats: seats });
}
Once you do that, everything should work! It also explains why you can see the components in the DOM, but onClickSeat has its state undefined (it's because this in onClickSeat is NOT referring to the class instance as you were expecting)

Mobx store update when React props change

I'm creating a generic react component, I'm using mobx internally to control the component state. What I need to achieve is besides to keep all business logic in Store, when the user change the showSomething prop, the store should know it so fetchSomeStuffs runs and change anotherThing.
// Application using the component
#observer
class MyApplication extends React.Component {
#observable
showSomething = false;
changeThings = () => {
this.showSomething = true;
};
render() {
return (
<React.Fragment>
<button onClick={this.changeThings}>Change Show Something</button>
<MyComponent showSomething={showSomething} />
</React.Fragment>
);
}
}
class Store {
#observable
showSomething = false;
#observable
anotherThing = [];
#action
setShowSomething = value => {
this.showSomething = value;
};
// I'll dispose this later...
fetchSomeStuffs = autorun(() => {
const { showSomething } = this;
// Update some other stuffs
if (showSomething) {
this.anotherThing = [1, 2, 3];
} else {
this.anotherThing = [];
}
});
}
#observer
class MyComponent extends React.Component {
static propTypes = {
showSomething: PropTypes.bool
};
constructor() {
super();
this.store = new Store();
}
componentDidMount() {
const { setShowSomething } = this.store;
this.setSomethingDispose = autorun(() =>
setShowSomething(this.props.showSomething)
);
}
componentWillUnmount() {
this.setSomethingDispose();
}
render() {
return (
<Provider store={this.store}>
<MySubComponent />
</Provider>
);
}
}
#inject("store")
#observer
class MySubComponent extends React.Component {
render() {
const { showSomething, anotherThing } = this.props.store;
return (
<div>
MySubComponent
{showSomething && "Something is finally showing"}
{anotherThing.map((r, i) => {
return <div key={i}>{r}</div>;
})}
</div>
);
}
}
This is the way i found to achieve it, all the logic is in the Store and I use an autorun in the componentDidMount of my main component to always keep the showSomething variable of the store the same as the prop.
My doubt here is if this is a good practice or if there are better ways to do it?
Yes, there is a better way, using computed values.
One pattern is to use a private variable to hold the values and a computed value to expose what you want.
using this pattern you can implement list filters and all sorts of dynamic computations with a lot of agility, since MobX optimizes all computed values.
Just remember that to access a computed value you just have to read it as a property, not a function.
Ex:
// good
store.anotherThing
// bad
store.anotherThing()
class Store {
#observable
showSomething = false;
#observable
_anotherThing = [];
#action
setShowSomething = value => {
this.showSomething = value;
};
#computed get anotherThing() {
const { showSomething } = this;
// Update some other stuffs
if (showSomething) {
this._anotherThing = [1, 2, 3];
} else {
this._anotherThing = [];
}
}
}

MobX observable with dynamic data

I have the following class
export default class BaseStore {
#observable model ;
#action updateStore(propertyName, newValue) {
this.model[propertyName] = newValue;
}
}
In child classes I add layers to the observable model, such as :
model.payment.type = 'credit card'
My react component doesn't render automatically when this happen, it does however, if I has a top level data such as:
model.Type = 'CreditCard'
I am new to MobX, and read that I need to make use of map() but I am unable to find a decent example that explain how to use it.
If you know all the keys that the model will have, you can just initialize them with a null value, and the observer components will re-render.
Example (JSBin)
class BaseStore {
#observable model = {
type: null
};
#action updateStore(propertyName, newValue) {
this.model[propertyName] = newValue;
}
}
const baseStore = new BaseStore();
#observer
class App extends Component {
componentDidMount() {
setTimeout(() => baseStore.model.type = 'CreditCard', 2000);
}
render() {
return <div> { baseStore.model.type } </div>;
}
}
If you don't know all the keys of model beforehand, you can use a map like you said:
Example (JSBin)
class BaseStore {
model = observable.map({});
#action updateStore(propertyName, newValue) {
this.model.set(propertyName, newValue);
}
}
const baseStore = new BaseStore();
#observer
class App extends Component {
componentDidMount() {
this.interval = setInterval(() => {
const key = Math.random().toString(36).substring(7);
const val = Math.random().toString(36).substring(7);
baseStore.updateStore(key, val);
}, 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
return <div>
{ baseStore.model.entries().map(e => <div> {`${e[0]} ${e[1]}` } </div>) }
</div>;
}
}

ReactJS Uncaught ReferenceError function is not defined

I am trying to implement a custom validation in React using ES6 syntax.
import React, { Component } from 'react';
export default class Board extends Component {
constructor(props) {
super(props);
}
static propTypes = { count: validate };
validate(props, propName, componentName){
if (props[propName]) {
let value = props[propName];
if (typeof value === 'number') {
if (value > 100) {
return new Error("Value cannot be more than 100");
}
}
else{
return new Error('Count should be a number')
}
}
};
render() {
return (
<div className="board">{this.props.count}</div>
);
}
}
When I run this code, I get an error "Uncaught ReferenceError: validate is not defined". I will appreciate if someone could help me resolve this.
import React, { Component } from 'react';
export default class Board extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="board">{this.props.count}</div>
);
}
}
const validate = (props, propName, componentName) => {
if (props[propName]) {
let value = props[propName];
if (typeof value === 'number') {
if (value > 100) {
return new Error("Value cannot be more than 100");
}
}
else{
return new Error('Count should be a number')
}
}
};
Board.propTypes = {
count: validate
}
or more simple...
import React, { Component } from 'react';
export default class Board extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="board">{this.props.count}</div>
);
}
}
Board.propTypes = {
count: (props, propName, componentName) => {
if (props[propName]) {
let value = props[propName];
if (typeof value === 'number') {
if (value > 100) {
return new Error("Value cannot be more than 100");
}
}
else{
return new Error('Count should be a number')
}
}
}
}
You can’t access instance properties from static properties, so easiest solution would be to make validate static too.
static propTypes = { count: Board.validate }
static validate(props, propName, componentName) {
// ...
}
this.validate seems to work too but I don’t like the combination of static and using this.

Resources