Assinging value to variable outside class - reactjs

I am accessing JSON file in ComponentDidMount in class A, i need to access that result outside class and need to use that in Class B
let test;
console.log(test);
class CustomerPage extends React.Component {
componentDidMount(): void {
$.getJSON("/api/LocaleStrings")
.done(results => {
let JsonString = JSON.parse(results);
test = new LocalizedStrings(JsonString);
})
.fail(console.log.bind(console));
}
}
Here, console.log(test) yields undefined.

It seems to me that your console.log(test) gets executed before the AJAX call returns, and at that point it will be uninitialized (undefined). Place your console.log inside the done function.
You could store your AJAX result in your component's state:
class CustomerPage extends React.Component {
constructor(props) {
super(props);
this.state = { test: null };
}
componentDidMount(): void {
$.getJSON("/api/LocaleStrings")
.done(results => {
let JsonString = JSON.parse(results);
this.setState({
test: new LocalizedStrings(JsonString);
});
})
.fail(console.log.bind(console));
}
}

You need to have an "event" that notifies anyone who is interested that test is available:
interface CustomerPageProps {
onLocaleStringsLoaded?: (test:object) => void
}
class CustomerPage extends React.Component<CustomerPageProps> {
static defaultProps {
onLocaleStringsLoaded: () => {} //nothing by default
}
componentDidMount(): void {
$.getJSON("/api/LocaleStrings")
.done(results => {
let JsonString = JSON.parse(results);
const test = new LocalizedStrings(JsonString);
this.props.onLocaleStringsLoaded(test);
}).fail(console.log.bind(console));
}
}
Then at some point in your code you could have:
<CustomerPage onLocaleStringsLoaded={window.console.log.bind(window.console)} />
which will print to the console once the result is available.

I recommend reading up a bit more on how React components share data. The component that needs the data can have an input defined, in which you can pass the test variable. Or using a redux store (which could potentially be a little too complex for your application). If you really want to continue this route. You can always use the window object to set a global variable: window.test = 'bla';. This is available anywhere in the application with console.log(window.test);.
You would have to update your code to:
window.test = new LocalizedStrings(JsonString);.
Verifying that it is set can be done with an interval:
setInterval(function() {
console.log(window.test);
}, 100);

Related

MobX - Reaction inside class component

Today I started using MobX and the first problem I ran into is how to execute a function in a React class component whenever an Observable updates.
I am under the impression this can be achieved using a reaction, but I'm not sure how to make it work.
class MissionLog {
private _missions: Array<IMissionItem> = [];
public get missions() {
return this._missions;
}
constructor() {
makeAutoObservable(this);
}
// Example of a method that modifies the _missions array
public receiveMission(mission: IMissionItem) {
this._missions.push(mission);
}
}
export const missionLog = new MissionLog();
// Example of modifying the missions array
missionLog.receiveMission(someMission);
export const ObserverTest = observer(class _ObserverTest extends React.Component {
constructor(props: any) {
super(props);
// Executes the console.log at the start,
// but not when missionLog.missions changes.
autorun(() => {
console.log("Autorun", missionLog.missions);
})
// Never executes the console.log
reaction(
() => missionLog.missions,
(mission) => {
console.log("Reaction");
}
)
}
render() {
return (
// Accessing missionLog.missions here
// gives me the correct, updated data,
// so my setup should be fine.
)
}
});
I also tried to use intercept and observe instead of reaction, but also no result.

mobx: class method call when changing data

Сan I use the Mobx library to call a class method when data changes?
For example MyObject writes container['item'] = 10 and as a result the myaction method is called.
class MyElement extends Component<any> {
// modifiable data
container: any = [];
// method called when data (container) is modified
myaction() {
console.log('container was modified');
console.log(this.container);
}
render() {
<MyObject container = {this.container} />
}
}
decorate(MyElement, {
container: observable
} as any)
You could use reaction for example:
container = [];
componentDidMount() {
// save disposer function to use later inside componentWillUnmount
this.reactionDisposer = reaction(
() => this.container.length,
() => {
console.log('container was modified')
}
);
}
// Don't forget to dispose it when unmount
componentWillUnmount() {
this.reactionDisposer();
}
Codesandbox link: https://codesandbox.io/s/httpsstackoverflowcomquestions63864175-kjorh?file=/src/App.js
Also, technically you can do that with array container['item'] = 10, but I advise you not to use string keys with array. If you want to use string keys then you need to use object or a Map.
Other methods you could also use to achieve what you want:
Autorun - https://mobx.js.org/refguide/autorun.html
When (basically single use reaction) - https://mobx.js.org/refguide/when.html
Or lower level stuff like observe and intercept - https://mobx.js.org/refguide/observe.html

React this.props."function" is not a function

I spent a lot of time on stack overflow and I still can't find my error despite my research. I want to user a method from a external librairy called "mxGraph", and I want to add a listener of my graph component
Here is my error: "this.props.graph.getSelectionModel is not a function"
Here is my code:
class StyleModificationBar extends Component {
constructor(props) {
super(props);
this.state = {
fontValue: 16,
};
}
componentDidMount() {
this.initListener();
}
initListener = () => {
this.props.graph.getSelectionModel().addListener(mxEvent.CHANGE, this.onSelected);
};
onSelected = evt => {
console.log(evt.cells[0]);
};
}
I've tried everything, bind my initLister function in the constructor and I've tried again and again to bind my initLister function in the constructor and I've tried again and again.
Could someone help me?
The behavior is very strange, the first console.log (the one in the constructor) returns an empty object while the one in the increase function returns my mxGraph object and this function works perfectly.
class StyleModificationBar extends Component {
constructor(props) {
super(props);
this.state = {
fontValue: 16,
};
console.log(this.props.graph); // This one print a empty object
}
increaseFontValue = () => {
console.log(this.props.graph); // This one print a full mxGraph object
let cellProperties = this.props.graph.getCellStyle(this.props.graph.getSelectionCell());
this.setState({fontValue: parseInt(cellProperties.fontSize) + 1},
() => {
this.props.graph.setCellStyles("fontSize", this.state.fontValue)
})
}
decreaseFontValue = () => {
let cellProperties = this.props.graph.getCellStyle(this.props.graph.getSelectionCell());
this.setState({fontValue: parseInt(cellProperties.fontSize) - 1},
() => {
this.props.graph.setCellStyles("fontSize", this.state.fontValue)
})
}

Is a bad practice to have an object with internal state as an instance property in a React component?

Is a bad practice to have an object with internal state as an instance property in a React component?
for example
class PageCacher {
constructor(fetchMethod) {
this.fetchMethod = fetchMethod
this.pages = []
}
async getPage(page) {
if (this.pages[page]) {
return this.pages[page]
} else {
const result = await this.fetchMethod(page)
this.pages[page] = result
return result
}
}
}
class ItemList extends React.Component {
constructor(props) {
super(props)
this.pageCacher = new PageCacher(props.fetchServiceMethod)
}
hanldeFetchPage = (page) => {
this.pageCacher.getPage(page).then(result => {
this.setState({items: result})
})
}
}
PageCache keeps the pages requested stored and returns the result if present if not makes the service call.
Since you are initializing pageCacher in the constructor, it will only receive the props present at time of the component mounting.
This means that if any of the props change, pageCacher will NOT receive those updated props.
Link to docs on component constructor

What's different between two ways of defining React Component?

There're 2 ways to define a React component.
First one is like below.
class SomeComponent extends React.Component {
constructor (props) {
super(props)
this.state = {
someState: false
}
this._handleOnChangeState = this._handleOnChangeState.bind(this)
}
_handleOnChangeState (e) {
this.setState({ someState: e.target.value })
}
....
}
Second one is like below.
class SomeComponent extends React.Component {
state = {
someState: false
}
_handleOnChangeState = (e) => {
this.setState({ someState: e.target.value })
}
....
}
These two codes are the same function, but I guess there's some different something like memory usage or etc.
Can someone make it clearly? Thanks in advance!
This is a new proposal (class fields) for ES which is in stage 3 right now. To run a code written in this way you need a transpiler like Babel and an appropriate plugin.
Before transpile:
class A {
static color = "red";
counter = 0;
handleClick = () => {
this.counter++;
}
}
After transpile (with stage 2 on Babel Repl):
class A {
constructor() {
this.counter = 0;
this.handleClick = () => {
this.counter++;
};
}
}
A.color = "red";
In addition to the official proposal 2ality blog post is a good source to see what are the details.
Here is a reddit post if you have time to read the discussion storm what is the reason behind this proposal :)
The arrow function here is a different story. You can use instance properties without constructor and mix your code with standard functions. But when you want to use something like that this won't work:
class App extends React.Component {
state = { bar: "baz"}
foo() { console.log(this.state.bar) };
render() {
return <div><button onClick={this.foo}>Click</button></div>;
}
}
We need to bind our function in somehow like:
return <div><button onClick={this.foo.bind(this)}>Click</button></div>
But, binding our function in a JSX prop is no so good since it will create our function in each render.
One way to do this nicely bind in our constructor:
constructor(props) {
super(props);
this.foo = this.foo.bind( this );
}
But, if I have to write a constructor what is the point? This is why you see arrow functions everywhere where we define the classes like your second example. No need to bind to function thanks to arrow functions. But it is not directly related to this new proposal I think.
The first one is the traditional approach and the second one is when you babel-transform-class-properties plugin.
In the second type babel does the same thing under the hood, therefore it is a matter of convenience.

Resources