Modify states from a non react.component class - reactjs

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.

Related

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.

What is the difference between these methods in class

I'm new to react and I can not understand the difference between these two methods in a class
doSomething=()=>{
console.log("Something")
}
and
doSomething() {
console.log("Something")
}
Both looks like they do the same thing
Once again this has been introduced in new ES7 syntax. You can read more about it here
https://www.reactnative.guide/6-conventions-and-code-style/6.4-es7-features.html
Basically in old ES6 we had to write classes and binding methods like this (copied from the documentation)
class SomeComponent extends Component {
_incrementCounter() {
this.setState({count: this.state.count+1})
}
constructor() {
this._incrementCounter = this._incrementCounter.bind(this);
}
...
}
In new ES7 you can simply use arrow function
class SomeComponent extends Component {
_incrementCounter = () => {
this.setState({count: this.state.count+1})
}
...
}
It is up to you what you gonna use. Both ways are ok to use but as you can see ES7 syntax is much shorter and easier to read
doSomething=()=>{
console.log("Something")
}
The above one is using fat arrow functions. You can access this (the class instance) inside this function without binding.
The second one is just defining a function. You will not have access to this here. You cannot use this inside that function. To use this you need to bind the function in constructor or in some other place
Eg;
this.doSomething = this.doSomething.bind(this);
More on this keyword
This is not quite so React specific but it does have implications when passing these functions around to other contexts.
Classes in ES6 don't require binding to allow the use of this within their methods, for example the following is perfectly valid:
class TestClass {
constructor() {
this.variable = 'a variable';
}
method() {
console.log(this.variable)
}
}
const thing = new TestClass();
thing.method(); // will output 'a variable'
The reason you would specifically want to use an arrow function is so you can pass this function down to a component as a prop or use it as part of a button action. Once you have passed the method reference away it no longer has access to this.
class TestComponent extends Component {
constructor() {
this.variable = 'a variable';
}
method() {
console.log(this.variable)
}
render() {
return <AnotherComponent method={this.method} />
}
}
Calling this.method from inside <AnotherComponent> will produce an error. This is where the arrow function comes in.
class TestComponent extends Component {
constructor() {
this.variable = 'a variable';
}
method = () => {
console.log(this.variable)
}
render() {
return <AnotherComponent method={this.method} />
}
}
method now uses an arrow function and 'lexically binds' this which basically means it takes its this from its surrounding context, in this case the class (component) it has been defined in.

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.

Is the constructor still needed in React with autobinding and property initializers

I am refactoring an es6 class based React component that uses the normal constructor, and then binds methods, and defines state/attributes within that constructor. Something like this:
class MySpecialComponent extends React.Component {
constructor(props) {
super(props)
this.state = { thing: true }
this.myMethod = this.myMethod.bind(this)
this.myAttribute = { amazing: false }
}
myMethod(e) {
this.setState({ thing: e.target.value })
}
}
I want to refactor this so that I am autobinding the functions, and using property initializers for the state and attributes. Now my code looks something like this:
class MySpecialComponent extends React.Component {
state = { thing: true }
myAttribute = { amazing: false }
myMethod = (e) => {
this.setState({ thing: e.target.value })
}
}
My question is, do I still need the constructor? Or are the props also autobound? I would have expected to still need the constructor and included super(props), but my code seems to be working and I'm confused.
Thanks
From my understanding, you don't need to type out a constructor at all when using class properties (as in your second code example). The accepted answer states that you do need one if you "need to reference the props in your initial state object," but if you're using said class properties, then you're probably using Babel to transpile it, in which case a constructor is used, it's just being done behind the scenes. Because of this, you don't need to add a constructor yourself, even if you are using props in state.
See this aricle for better examples and a better explanation.
You don't need an explicitly defined constructor unless you need to reference the props in your initial state object.
You don't need to define a constructor explicitly , and then do super(props).You can access the props as in the example below. i.e. 'prop1'
class MySpecialComponent extends React.Component {
state = {
thing: true ,
prop1:this.props.prop1
}
myAttribute = { amazing: false }
myMethod = (e) => {
this.setState({ thing: e.target.value })
}
render(){
console.log(this.state.prop1);
return(
<div>Hi</div>
);
}
}
ReactDOM.render(<MySpecialComponent prop1={1}/> , mountNode);

componentDidMount() not being called when react component is mounted

I've been attempting to fetch some data from a server and for some odd reason componentDidMount() is not firing as it should be. I added a console.log() statement inside of componentDidMount() to check if it was firing. I know the request to the server works as it should As I used it outside of react and it worked as it should.
class App extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
obj: {}
};
};
getAllStarShips () {
reachGraphQL('http://localhost:4000/', `{
allStarships(first: 7) {
edges {
node {
id
name
model
costInCredits
pilotConnection {
edges {
node {
...pilotFragment
}
}
}
}
}
}
}
fragment pilotFragment on Person {
name
homeworld { name }
}`, {}). then((data) => {
console.log('getALL:', JSON.stringify(data, null, 2))
this.setState({
obj: data
});
});
}
componentDidMount() {
console.log('Check to see if firing')
this.getAllStarShips();
}
render() {
console.log('state:',JSON.stringify(this.state.obj, null, 2));
return (
<div>
<h1>React-Reach!</h1>
<p>{this.state.obj.allStarships.edges[1].node.name}</p>
</div>
);
}
}
render(
<App></App>,
document.getElementById('app')
);
The issue here is that the render method is crashing, because the following line is generating an error
<p>{this.state.obj.allStarships.edges[1].node.name}</p>
Fix this to not use this.state.obj.allStarships.edges[1].node.name directly, unless you can guarantee that each receiver is defined.
Check your component's key
Another thing that will cause this to happen is if your component does not have a key. In React, the key property is used to determine whether a change is just new properties for a component or if the change is a new component.
React will only unmount the old component and mount a new one if the key changed. If you're seeing cases where componentDidMount() is not being called, make sure your component has a unique key.
With the key set, React will interpret them as different components and handle unmounting and mounting.
Example Without a Key:
<SomeComponent prop1={foo} />
Example with a Key
const key = foo.getUniqueId()
<SomeComponent key={key} prop1={foo} />
Also check that you don't have more than one componentDidMount if you have a component with a lot of code. It's a good idea to keep lifecycle methods near the top after the constructor.
I encountered this issue (componentDidMount() not being called) because my component was adding an attribute to the component state in the constructor, but not in the Component declaration. It caused a runtime failure.
Problem:
class Abc extends React.Component<props, {}> {
this.state = { newAttr: false }; ...
Fix:
class Abc extends React.Component<props, {newAttr: boolean}> {
this.state = { newAttr: false }; ...

Resources