React: Define constants? - reactjs

I have a container component which is feeding a presentation component with one image at a time (every time a user clicks a button). I want to define those image paths in a constant (array). But I get TypeError: Cannot read property '1' of undefined when trying to read from that array.
How can I solve that error, and is this the best approach?
export default class ImageContainer extends Component {
constructor(props){
super(props)
this.state = {
currentImage: '/images/dummy-image-1.jpg'
}
this.changeImage = this.changeImage.bind(this)
const images = [
'/images/dummy-image-1.jpg',
'/images/dummy-image-2.jpg',
'/images/dummy-image-3.jpg',
'/images/dummy-image-4.jpg',
'/images/dummy-image-5.jpg'
]
}
changeImage(event){
console.log(this.images[1])
//Do something
}
render(){
return(
<ImageView image={this.state.currentImage} changeImage={this.changeImage} />
)
}
}

You need to define images as object property in constructor:
this.images = [
'/images/dummy-image-1.jpg',
...
];
when you declare variables (using var, let, const) it is visible only in function (in your case constructor) scope.

You could move the const definition outside of the class:
const images = [
'/images/dummy-image-1.jpg',
'/images/dummy-image-2.jpg',
'/images/dummy-image-3.jpg',
'/images/dummy-image-4.jpg',
'/images/dummy-image-5.jpg'
];
export default class ImageContainer extends Component {
//...
}
This way images remains a const and it's still only visible to the code within the file (which I'll assume is just the ImageContainer class).

Related

React Respond to Property Object Change

I'm looking for a way to update a React.Component when the internal state of one of its properties (an object) changes.
Example:
export class ParentClass extends React.Component {
constructor(props) {
super(props);
this.someObject = new SomeObject();
}
...
render() {
return <ChildClass someObject=this.someObject />
}
}
When I call this.someObject.change() in the parent class. How can I signal to the child class that it should re-render since the internal state of someObject just changed?
Normally, if I updated a primitive value, I believe this would trigger a re-render of the child. However, since the object pointer hasn't changed, I don't believe the child class is aware a change has occurred.
Is it possible to trigger a re-render of ChildClass in this case? Is there maybe a better convention for managing this relationship?
This could have a performance impact, but I resolved this by moving someObject into the state of the parent and setting the Child property to {this.state.someObject}.
When someObject (in this case roadTileMatrix) changes, I clone the object, make the changes, then set the new object as the state. This seems to be a common pattern from my reading.
Alternatively, as a more lightweight solution, it looks like you could instead set a key value in the state that will change each time changes are made to someObject, triggering a re-render of the child.
Code here:
import React from 'react'
import PropTypes from 'prop-types';
import { RoadTileMatrix, Grid } from './Grid';
import { TravelGraph } from './TravelGraph';
export class RoadNetwork extends React.Component {
constructor(props) {
super(props);
const rows = this.props.rows;
const cols = this.props.cols;
this.state = {
roadTileMatrix: new RoadTileMatrix(rows, cols, null)
};
this.addTile = this.addTile.bind(this);
}
addTile(r, c) {
const rows = this.props.rows;
const cols = this.props.cols;
const oldArray = this.state.roadTileMatrix.innerArray;
const newRoadTileMatrix = new RoadTileMatrix(rows, cols, oldArray);
newRoadTileMatrix.addTile(r, c, true);
this.setState({
roadTileMatrix: newRoadTileMatrix,
});
}
render() {
return (
<div>
<Grid roadTileMatrix={this.state.roadTileMatrix} addTile={this.addTile} />
<TravelGraph />
</div>
);
};
}
RoadNetwork.propTypes = {
rows: PropTypes.number.isRequired,
cols: PropTypes.number.isRequired,
}

Modify states from a non react.component class

In my file I have a Main class which extends React.Component. The class is exported at the end of the file. It has some states, one of which is a list (Object[]).
Within the file there is another class (let's call it Foo) which has other functions. I create a reference to Foo within Main, and then use that to call bar() within Foo.
In bar, I have a for loop which should add 5 TestComponents to that list mentioned above.
Since it's a state, I also have Foo extending React.Component. The issue is that even though that allows me to use setState(), the component is never mounted so when you modify the state you get:
Warning: Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to `this.state` directly or define a `state = {};` class property with the desired state in the Foo component.
If I remove the extends React.Component from Foo then of course, I can't change the state since I can't use setState.
Here is a very simplified version of my main code:
interface State {
list: Object[];
}
class Main extends React.Component<{}, State> {
foo = new Foo();
state = {
list: [],
}
componentDidMount() {
console.log(this.state.list); //logs: []
this.foo.bar();
console.log(this.state.list); //logs: [] expected: [TestComponent....]
}
render() {
return (
<IonContent>
<IonList>
{
//this.list.map(...);
}
</IonList>
</IonContent>
);
};
};
class Foo extends React.Component<{}, State> {
state = {
list: [],
}
constructor(props) {
super(props);
}
componentDidMount() {
console.log("mounted"); //doesnt log this since component doesnt get mounted
}
bar() {
for(let i = 0; i < 5; i++) {
let a = <TestComponent key={i}></TestComponent>
this.setState({
list: [...this.state.list, a], //errors here saying component not mounted
});
}
}
}
export default Main;
How can I modify the list state from within Foo since Foo is never mounted as a component ('mounted' is never logged)?
I tried seeing if a render function would mount it but like I thought it didn't.
render() {
return(
<></>
);
}
I got this to work using globals (this should solve two problems at once. I needed the array 'global' within a single script but also within every script)
I did this by creating a globals.tsx file.
Within the file I added:
export var bluetoothDevices: Object[] = [];
then to access it the global you can do:
import { bluetoothDevices } from '/globals/globals'
bluetoothDevices.push(x);
and it's as simple as just pushing anything to the array. I can confirm this works within the file (I can modify and read it from each class) although I can't confirm if it works throughout all scripts since I only have 1 page at the moment.

How do you access React statics within instance methods?

I'm using React 16.13.0. I have defined this static block within my component ...
class FormContainer extends Component {
statics: {
DEFAULT_COUNTRY: 484;
}
constructor(props) {
super(props);
...
componentDidMount() {
let initialCountries = [];
let initialProvinces = [];
// Get initial countries
fetch('/countries/')
.then(response => {
return response.json();
}).then(data => {
initialCountries = data.map((country) => {
return country
});
console.log("output ...");
console.log(initialCountries);
this.setState({
countries: initialCountries,
});
});
// Get initial provinces (states)
console.log("val:" + this.DEFAULT_COUNTRY);
My question is, how do I reference that static block? The above
console.log("val:" + this.DEFAULT_COUNTRY);
produces
undefined
Confusion comes from old React.createClass function that you would use if your runtime didn't support classes as a Javascript feature. You would pass an object in React.createClass and React would create sort-of-a-class for that component. There, statics property on that object would serve like an object with all static properties of that pseudo class:
// old
const MyComponent = React.createClass({
statics: {
DEFAULT_COUNTRY: 484
},
render: function() {}
})
There is no real class going on here, it's just an object inside an object, and it is indeed easy to confuse with e.g. static block in Java
With ES6 classes (which you are using) static properties are declared like this
class MyComponent extends React.Component {
static DEFAULT_COUNTRY = 484
static ANOTHER_STATIC_PROPERTY = 23
render () {}
}
And can be accessed as MyComponent.DEFAULT_COUNTRY anywhere
You are most likely using Babel, in that case, babel-plugin-proposal-class-properties should be enabled, as not all browsers support this feature. Node without Babel supports class properties from version 12
There are no static blocks in Javascript per se, but you can modify the class from static context from outside, e.g.
class MyComponent extends React.Component {
static DEFAULT_COUNTRY = 484
static ANOTHER_STATIC_PROPERTY = 23
render () {}
}
MyComponent.HELLO = 'world'
Let’s use:
static DEFAULT_COUNTRY = 484
With static you can assign a property/method to the class function itself, not to its prototype. The value of this in FormContainer.DEFAULT_COUNTRY is the class constructor FormContainer itself.
You can access it from within the class as this.constructor.DEFAULT_COUNTRY. And as FormContainer.DEFAULT_COUNTRY within the class and out of it.
So, console.log("val:" + this.constructor.DEFAULT_COUNTRY);
Consider the following as an options to store DEFAULT_COUNTRY`:
class FormContainer extends Component {
constructor(props) {
super(props);
this.DEFAULT_COUNTRY = 484;
}
render(){
console.log(this.DEFAULT_COUNTRY)
...
}
};
or
class FormContainer extends Component {
DEFAULT_COUNTRY = 484;
render(){
console.log(this.DEFAULT_COUNTRY)
...
}
};
or, this could be also an option:
class FormContainer extends Component {
statics = {
DEFAULT_COUNTRY: 484,
};
render(){
console.log(this.statics.DEFAULT_COUNTRY)
...
}
};
But in the last example, statics is not key word, but just a name of class field. Hope this will help you.
Actually the declaration has an issue, you should use below code:
class FormContainer extends Component {
statics = { // use equal sign not colon sign
DEFAULT_COUNTRY: 484, // use comma here not semicolon
};
Then in everywhere of FormContainer class, you can access by this.statics, for your default country you can access by this.statics.DEFAULT_COUNTRY.
By using the colon for declaring the statics variable of the class, you just get undefined.
Hint: do not use static keyword. it defines a static variable for the class that is not accessible inside the class. in ReactJS the static keyword often use for declaring prop types of class props members.
Update
To prove this correctness of code: see the IDE and the Browser
If you got an error, please show your code, maybe you call it in an irrelevant place.

Where should a state be defined?

What is the difference between these two constructs of defining state in React?
class ProductsPage extends Component {
constructor(props) {
super(props);
this.state = {
products: []
};
}
...
}
and this:
class ProductsPage extends Component {
state = {
products: []
};
...
}
Both of them work well when coded in ES6. However, the lower one doesn't seem to work in typescript 3. I have the following:
interface IState {
products: IProduct[];
}
class ProductsPage extends Component<{}, IState> {
state = {
products: []
};
public componentDidMount() {
this.setState({ products });
}
public render() {
return (
<div className="page-container">
<ul className="product-list">
{this.state.products.map(p => (
<li className="product-list-item" key={p.id}>
{p.name}
</li>
))}
</ul>
</div>
);
}
}
and the ts compiler flagged an error saying: Property id does not exist on type 'never'
Why is that?
The second form is class properties which is a stage 3 JavaScript proposal (meaning it's not part of the language yet). Adding properties on the constructor is the old ES2015 way (called maximally minimal classes).
In your case there is no functional difference.
TypeScript requires you to declare class fields for type safety - hence the warning.
How to define the state for a React component is as subjective as the coding styles React promotes itself. Usually I go the very strict route which looks as follows:
type State = {
someStateVar: number[];
};
export class MyComponent extends Component<{}, State> {
public readonly state: State = {
someStateVar: [],
};
public async componentDidMount() {
// Dynamically fetch data (maybe via axios) and populate the new state
const data = await axios.get<number[]>(...);
this.setState({ someStateVar: data });
}
}
As you can see, I explicitly mark state as readonly just make sure, nobody attempts to write directly to it (even though IDEs and linters can check for those errors without the precaution nowadays).
Another reason why I prefer to not set the state manually with an assigment is that it might encourage wrong handling of state. You are never to assign something directly to state in a class method without the use of setState.
Furthermore I am basically defining the defaults right away and populating the state with dynamic data in componentDidMount. This approach allows you to keep the code concise by dropping an explicit constructor definition as you should move such an initialization to componentDidMount anyways and use the constructor only for binding methods if you don't use the class member arrow notation for those in the first place.

Getting a reference to the class instance of a component

I have a typescript class that extends React.Component:
class MyComponent extends React.Component<{}, {}>
{
constructor(props: {})
{
super(props);
}
public render()
{
return <span>Test</span>;
}
public MyMethod() {
console.log("Working fine");
}
}
Then there is a place where I manually have to create an instance and attach this to the DOM:
var component = MyComponent;
var element = React.createElement(component, {}, null);
ReactDOM.render(element, myDomElementContainer);
Due to architectural constraints of the system, I need to store a reference to my class instance for that component for later use, problem is that I can not find any reference to the instance of my class in the created element, it only have a reference to the class via the property type.
React.createElement is only allowing me to supply the class, and ReactDOM.render does not like a manually instantiated object.
What should I in order to instantiate a custom component, attach it to the DOM and get a reference to the instance of my component class?
You have a few options:
(1) Use the return value from ReactDOM.render:
var element = ReactDOM.render(<MyComponent />, myDomElementContainer);
(2) Use React.createElement:
var element = React.createElement(MyComponent);
ReactDOM.render(element);
(3) Use refs:
var element;
ReactDOM.render(<MyComponent ref={el => element = el } />, myDomElementContainer);
The instance will be assigned to element when the instance of MyComponent has been rendered.
For my scenario I used a singleton (static variable)
export let instance = null;
class SearchDialogue extends React.Component {
constructor(props) {
super(props);
instance = this;
}
}
then when you need to reference it you can run
import {instance as searchDialogueInstance} from './SearchDialogue'
console.log(searchDialogueInstance.someFooBar);
Note
Keep in mind that this only works WELL if you're leveraging the singleton design pattern. If you are not then you could turn the static var into an array and on push new instances into the array. Careful as this could get you into trouble if you are not already familiar with potential memory pitfalls.
SearchDialogue.instances.push(this);
Looks like you use React.js in a way you using jQuery, and it is not ok. Really, I cannot imagine case that requires doing such things; and I suspect that you should not do this. Please, (1) use jsx to create components, (2) use refs to found component at that jsx. Like
...
x(){
this.refs.c.style={color:'blue'}
}
render() {
return <div ref='c' style={{border:'1px solid red', height:10}} onClick={this.x.bind(this)}/>
}

Resources