Why and how is state being set this way inside `getDerivedStateFromError`? - javascript

I'm learning about ErrorBoundaries in React and was wondering why and how in the getDerivedStateFromError method, is state being set this way?
// REACT
import React from 'react';
export class ErrorBoundary extends React.Component {
state = {
isThereError: false
}
static getDerivedStateFromError(error) {
return {
isThereError: true
};
}
// ...
};
Shouldn't it be:
// REACT
import React from 'react';
export class ErrorBoundary extends React.Component {
state = {
isThereError: false
}
static getDerivedStateFromError(error) {
this.setState({ isThereError: true })
}
// ...
};

State isn't being set in the method itself. But by convention the framework expects that method to return an updated state object. The framework then internally uses that result to update state:
This lifecycle is invoked after an error has been thrown by a descendant component. It receives the error that was thrown as a parameter and should return a value to update state.
So when implementing getDerivedStateFromError in your component you don't need to worry about updating state within that method. Just return the new state.

Related

Can i update Context API state in javascript class?

I have a Bluetooth class and listener method. I want to update my state in Bluetooth class and i will show in functional component.
JavaScript Class
import React, { Component } from "react";
import { MySampleContext } from "../../contexts/MySampleContext";
export class BluetoothClass extends Component {
static contextType = MySampleContext;
sampleBluetoothListener(value){
this.context.updateMyState(value);
}
}
This is my error.
TypeError: undefined is not an object (evaluating 'this.context.updateMyState')
Yes you can, you can pass data down to your component tree using Context API.
// Context file
import React from 'react';
const FormContext = React.createContext();
export default FormContext;
// Parent Class Component
import FormContext from "../context";
class ParentClass extends Component {
state = { name: "John Doe" };
render() {
return (
<FormContext.Provider value={this.state.name}>
<ChildClass />
</FormContext.Provider>
);
}
}
// Child Class Component
import FormContext from "../context";
class ChildClass extends Component {
render() {
return (
<FormContext.Consumer>
{(context) => {
console.log(context);
}}
</FormContext.Consumer>
);
}
}
The value prop in the context API takes an object, in which you could add a method that changes your state and pass it down to your component tree.
I advice you to take a quick look at the official Context API docs by React for a better understanding.
Thanks for all answers. But i mean pure "javascript class" not component, without rendering and react hooks is component based. Finally i solve problem with call back functions.

How to use mobx-react 'observer' without decorator syntax?

I am trying to shoe horn mobx into a vr application I am making with react 360. I've tried to use the decorator syntax but after wasting a solid chunk of time trying to implement it, I've decided to use the nondecorator syntax. Here is an example that I came across from the mobx documentation that I have a question on. Here is the code:
import {observer} from "mobx-react";
var timerData = observable({
secondsPassed: 0
});
setInterval(() => {
timerData.secondsPassed++;
}, 1000);
#observer class Timer extends React.Component {
render() {
return (<span>Seconds passed: { this.props.timerData.secondsPassed } </span> )
}
};
ReactDOM.render(<Timer timerData={timerData} />, document.body);
Notice the observer declaration on the Timer class. The documentation states this.
Note that using #observer as decorator is optional, observer(class Timer ... { }) achieves exactly the same.
Would this be the correct way of implementing Timer?
observer(class Timer extends React.Component {
render() {
return (<span>Seconds passed: { this.props.timerData.secondsPassed } </span> )
}
})
Concerning the code snippet you have added I don't know whether it is a valid way or not, but here's the way I'm using MobX without the decorator syntax in my application project:
Create your MobX store, say MyStore.js like the following:
import {observable, action, computed, decorate} from 'mobx';
export default class MyStore {
storeMap = observable(new Map());
storeArray = observable([]);
storeBoolean = false
get storeMapSize() {
return this.storeMap.size;
}
setStoreBoolean(value) {
this.storeBoolean = value;
}
}
decorate(MyStore, {
storeMap: observable,
storeArray: observable,
storeBoolean: observable
storeMapSize: computed,
setStoreBoolean: action
});
Then in your component Timer.js:
import {inject, observer} from "mobx-react";
class Timer extends React.Component {
render() {
return (<span>value from store: { this.props.myStore.storeMap.get('any_key') } </span> )
}
}
export default inject('myStore')(observer(Timer));
and you can create as many stores as you want and inject all of them to your components using the same inject method and use them in the same way via this.props, for example
export default inject('myStore', 'anotherStore', 'anotherNewStore')(observer(Timer));

React: componentDidMount + setState not re-rendering the component

I'm fairly new to react and struggle to update a custom component using componentDidMount and setState, which seems to be the recommended way of doing it. Below an example (includes an axios API call to get the data):
import React from 'react';
import {MyComponent} from 'my_component';
import axios from 'axios';
export default class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
GetData() {
return axios.get('http://localhost:5000/<route>');
}
componentDidMount() {
this.GetData().then(
(resp) => {
this.setState(
{data: resp.data}
)
}
)
}
render() {
return (
<MyComponent data={this.state.data} />
);
}
}
Doing console.log(this.state.data) just below render() shows that this.state.data does indeed get updated (from [] to whatever the API returns). However, the problem appears to be that MyComponent isn't rendered afresh by componentDidMount. From the Facebook react docs:
Setting state in this method will trigger a re-rendering.
This does not seem to be the case here: The constructor of MyComponent only gets called once (where this.props.data = []) and the component does not get rendered again. I'd be great if someone could explain why this is and whether there's a solution or a different way altogether to get the updating done.
UPDATE
I've added the code for MyComponent (minus some irrelevant features, as indicated by ...). console.log(data_array) prints an empty array.
import React from 'react';
class DataWrapper {
constructor(data) {
this._data = data;
}
getSize() {
return this._data.length;
}
...
}
export class MyComponent extends React.Component {
constructor(props) {
super(props);
this._dataWrapper = new DataWrapper(this.props.data);
this.state = {
data_array: this._dataWrapper,
};
}
render() {
var {data_array} = this.state;
console.log(data_array);
return (
...
);
}
}
You are falling victim to this antipattern.
In MyComponent constructor, which only gets called the first time it mounts, passed your empty array through new DataWrapper and now you have some local state which will never be updated no matter what your parent does.
It's always better to have one source of truth, just one state object anywhere (especially for things like ajax responses), and pass those around via props. In fact this way, you can even write MyComponent as a simple function, instead of a class.
class Example extends Component {
state = { data: [] }
GetData() { .. }
componentDidMount() {
this.GetData().then(res =>
this.setState({data: new DataWrapper(res.data)})
)
}
render() { return <MyComponent data={this.state.data} /> }
}
...
function MyComponent (props) {
// props.data will update when your parent calls setState
// you can also call DataWrapper here if you need MyComponent specific wrapper
return (
<div>..</div>
)
}
In other words what azium is saying, is that you need to turn your receiving component into a controlled one. Meaning, it shouldn't have state at all. Use the props directly.
Yes, even turn it into a functional component. This helps you maintain in your mind that functional components generally don't have state (it's possible to put state in them but ... seperation of concerns).
If you need to edit state from that controlled component, provide the functions through props and define the functions in the "master" component. So the master component simply lends control to the children. They want anything they talk to the parent.
I'm not posting code here since the ammendment you need to make is negligible. Where you have this.state in the controlled component, change to this.props.

ReactJs / Javascript - Can a HOC read the parameter component's props?

I have a Higher Order Component that receives another component as a parameter:
HOC
export default function HOC(Comp) {
return class extends Component {
doSomething() {
const temp = // the Comp's clientId prop???
}
........
}
}
Sub Component
#HOC
export default class SubComponent extends Component {
.....
static proptypes = {
clientId: PropTypes.string.isRequired
};
.......
}
Question:
Is it possible in the scenario above for the HOC to be aware of SubComponent's clientId property in its arguements and if so, how can I make the HOC aware of it for my doSomething function?
Since it's really the HOC that receives the props (or rather the component the function returns), you can just access them with this.props:
const temp = this.props.clientId;

Reactjs, parent component, state and props

I m actually learning reactjs and I m actually developping a little TODO list, wrapped inside of a "parent component" called TODO.
Inside of this parent, I want to get the current state of the TODO from the concerned store, and then pass this state to child component as property.
The problem is that I dont know where to initialize my parent state values.
In fact, I m using ES6 syntax, and so, I dont have getInitialState() function. It's written in the documentation that I should use component constructor to initialize these state values.
The fact is that if I want to initialize the state inside of my constructor, the this.context (Fluxible Context) is undefined actually.
I decided to move the initialization inside of componentDidMount, but it seems to be an anti pattern, and I need another solution. Can you help me ?
Here's my actual code :
import React from 'react';
import TodoTable from './TodoTable';
import ListStore from '../stores/ListStore';
class Todo extends React.Component {
constructor(props){
super(props);
this.state = {listItem:[]};
this._onStoreChange = this._onStoreChange.bind(this);
}
static contextTypes = {
executeAction: React.PropTypes.func.isRequired,
getStore: React.PropTypes.func.isRequired
};
componentDidMount() {
this.setState(this.getStoreState()); // this is what I need to move inside of the constructor
this.context.getStore(ListStore).addChangeListener(this._onStoreChange);
}
componentWillUnmount() {
this.context.getStore(ListStore).removeChangeListener(this._onStoreChange);
}
_onStoreChange () {
this.setState(this.getStoreState());
}
getStoreState() {
return {
listItem: this.context.getStore(ListStore).getItems() // gives undefined
}
}
add(e){
this.context.executeAction(function (actionContext, payload, done) {
actionContext.dispatch('ADD_ITEM', {name:'toto', key:new Date().getTime()});
});
}
render() {
return (
<div>
<button className='waves-effect waves-light btn' onClick={this.add.bind(this)}>Add</button>
<TodoTable listItems={this.state.listItem}></TodoTable>
</div>
);
}
}
export default Todo;
As a Fluxible user you should benefit from Fluxible addons:
connectToStores.
The following example will listen to changes in FooStore and BarStore and pass foo and bar as props to the Component when it is instantiated.
class Component extends React.Component {
render() {
return (
<ul>
<li>{this.props.foo}</li>
<li>{this.props.bar}</li>
</ul>
);
}
}
Component = connectToStores(Component, [FooStore, BarStore], (context, props) => ({
foo: context.getStore(FooStore).getFoo(),
bar: context.getStore(BarStore).getBar()
}));
export default Component;
Look into fluxible example for more details. Code exсerpt:
var connectToStores = require('fluxible-addons-react/connectToStores');
var TodoStore = require('../stores/TodoStore');
...
TodoApp = connectToStores(TodoApp, [TodoStore], function (context, props) {
return {
items: context.getStore(TodoStore).getAll()
};
});
As a result you wouldn't need to call setState, all store data will be in component's props.

Categories

Resources