I am building a React + Mobx app and I have a situation where I have a component that does not render anything because it is all based on a 3rd party map API, but I need to react to some store property changes:
componentDidMount() {
mapApi.doSomething(this.props.store.value)
}
componentWillReact() {
mapApi.doSomething(this.props.store.value)
}
render () {
//workaround to make componentWillReact trigger
console.log(this.props.store.value)
return null
}
Is there an elegant way to implement this?
If I understood you correctly, you want to react to something that not used in render function. You can do something like this:
#observer
class ListView extends React.Component {
constructor(props) {
super(props)
}
#observable filterType = 'all'
handleFilterTypeChange(filterType) {
fetch('http://endpoint/according/to/filtertype')
.then()
.then()
}
// I want the method that autoruns whenever the #observable filterType value changes
hookThatDetectsWhenObservableValueChanges = reaction(
() => this.filterType,
filterType => {
this.handleFilterTypeChange(filterType);
}
)
}
This solution is mentioned here https://github.com/mobxjs/mobx-react/issues/122#issuecomment-246358153
You could use shouldComponentUpdate
shouldComponentUpdate() {
return false; // would never rerender.
}
Related
I am working on an application and when i had the ...this.state in my componentWillRecieveProps my code was not working correctly.
I am not 100% sure why this was affecting my state. Is it because i am always appending the current state when theres no need to do that.
componentWillReceiveProps(nextProps) {
// this.setState({ ...this.state, ...nextProps })
this.setState({...nextProps})
}
The componentWillReceiveProps lifecycle function is deprecated. Please use the getDerivedStateFromProps static method instead. In your case it will look like this:
class Example extends React.Component {
state = {
...
}
static getDerivedStateFromProps(nextProps) {
return {...nextProps}
}
render() {
...
}
}
However in real life it is aimless to convert every props to state without using any logic. In this case it is better to reach the props directly via this.props.
I think this question has been answer several time but I can't find my specific case.
https://codesandbox.io/s/jjy9l3003
So basically I have an App component that trigger an action that change a state call "isSmall" to true if the screen is resized and less than 500px (and false if it is higher)
class App extends React.Component {
...
resizeHandeler(e) {
const { window, dispatch } = this.props;
if (window.innerWidth < 500 && !this.state.isSmall) {
dispatch(isSmallAction(true));
this.setState({ isSmall: true });
} else if (window.innerWidth >= 500 && this.state.isSmall) {
dispatch(isSmallAction(false));
console.log(isSmallAction(false));
this.setState({ isSmall: false })
}
};
componentDidMount() {
const { window } = this.props;
window.addEventListener('resize', this.resizeHandeler.bind(this));
}
...
I have an other component called HeaderContainer who is a child of App and connected to the Store and the state "isSmall", I want this component to rerender when the "isSmall" change state... but it is not
class Header extends React.Component {
constructor(props) {
super(props);
this.isSmall = props.isSmall;
this.isHome = props.isHome;
}
...
render() {
return (
<div>
{
this.isSmall
?
(<div>Is small</div>)
:
(<div>is BIG</div>)
}
</div>
);
}
...
even if I can see through the console that redux is actually updating the store the Header component is not re-rendering.
Can someone point out what I am missing ?
Am I misunderstanding the "connect()" redux-react function ?
Looking at your code on the link you posted your component is connected to the redux store via connect
const mapStateToProps = (state, ownProps) => {
return {
isHome: ownProps.isHome,
isSmall: state.get('isSmall')
}
}
export const HeaderContainer = connect(mapStateToProps)(Header);
That means that the props you are accessing in your mapStateToProps function (isHome and isSmall) are taken from the redux store and passed as props into your components.
To have React re-render your component you have to use 'this.props' inside the render function (as render is called every time a prop change):
render() {
return (
<div>
{
this.props.isSmall
?
(<div>Is small</div>)
:
(<div>is BIG</div>)
}
</div>
);
}
You are doing it well in the constructor but the constructor is only called once before the component is mounted. You should have a look at react lifecycle methods: https://reactjs.org/docs/react-component.html#constructor
You could remove entirely the constructor in your Header.js file.
You should also avoid using public class properties (e.g. this.isSmall = props.isSmall; ) in react when possible and make use of the React local state when your component needs it: https://reactjs.org/docs/state-and-lifecycle.html#adding-local-state-to-a-class
A component is only mounted once and then only being updated by getting passed new props. You constructor is therefore only being called once before mount. That means that the instance properties you set there will never change during the lifetime of your mounted component. You have to directly Access this.props in your render() function to make updating work. You can remove the constructor as he doesn't do anything useful in this case.
We are currently refactoring to use higher-order components. For the most part this is making everything much simpler.
We have HOCs for fetching data and listening to stores. For example, we have connectStores, which takes a list of stores to subscribe to and a function to fetch the data (to pass as extra props):
connectStores(FooComponent, [FooStore], function (props) {
return {
foo: FooStore.get(props.id),
};
});
However, there are a few places where the process of fetching the data from the store depends upon the state. For example, we have a SelectFooPopup the presents the user with a list of items to select from. But there is also a search box to filter the list, so at the moment the component listens directly to the store and then fetches the data itself like this:
componentDidMount() {
var self = this;
this.listenTo(FooStore, 'change', function () {
self.forceUpdate();
});
}
render() {
var items = FooStore.search(this.state.searchText);
// render...
}
(this.listenTo is a mixin which we're trying to replace with HOCs so we can use ES6 classes)
I can think of a few options, but I don't like any of them:
Option 1: Remove listenTo and cleanup the listener manually
componentDidMount() {
var self = this;
this.listener = function () {
self.forceUpdate();
};
FooStore.on('change', this.listener);
}
componentWillUnmount() {
if (this.listener) {
FooStore.removeListener('change', this.listener);
}
}
render() {
var items = FooStore.search(this.state.searchText);
// render...
}
I really hate having to do this manually. We did this before we had the listenTo mixin and it's far too easy to get wrong.
This also doesn't help when the subscription has to fetch the data from the server directly rather than using a pre-filled store.
Option 2: Use connectStores but don't return any extra data
class SelectFooPopup extends React.Component {
render() {
var items = FooStore.search(this.state.searchText);
}
}
connectStores(SelectFooPopup, [FooStore], function (props) {
// Just to forceUpdate
return {};
});
This just feels wrong to me. This is asking for trouble when we start optimising for pure components and suddenly the child component doesn't re-render anymore.
Option 3: Use connectStores to fetch all the data and then filter it in render
class SelectFooPopup extends React.Component {
render() {
var items = filterSearch(this.props.items, this.state.searchText);
}
}
connectStores(SelectFooPopup, [FooStore], function (props) {
return {
items: FooStore.getAllItems(),
};
});
But now I have to have a completely separate filterSearch function. Shouldn't this be a method on the store?
Also, it doesn't make much difference in this example, but I have other components with a similar issue where
they are fetching data from the server rather than subscribing to a pre-filled store. In these cases the
data set is far too large to send it all and filter later, so the searchText must be available when fetching the data.
Option 4: Create a parent component to hold the state
Sometimes this is the right solution. But it doesn't feel right here. The searchText is part of the state of this component. It belongs in the same place that renders the search box.
Moving it to a separate component is confusing and artificial.
Option 5: Use a "parentState" HOC
function parentState(Component, getInitialState) {
class ParentStateContainer extends React.Component {
constructor(props) {
super();
this.setParentState = this.setParentState.bind(this);
if (getInitialState) {
this.state = getInitialState(props);
} else {
this.state = {};
}
}
setParentState(newState) {
this.setState(newState);
}
render() {
return <Component {...this.props} {...this.state} setParentState={ this.setParentState } />;
}
}
return ParentStateContainer;
}
// Usage:
parentState(SelectFooPopup, function (props) {
return {
searchText: '',
};
});
// In handleSearchText:
this.props.setParentState({ searchText: newValue });
This also feels really wrong and I should probably throw this away.
Conclusion
In React we have 2 levels: props and state.
It seems to me that there are actually 4 levels to think about:
props
data that depends on props only
state
data that depends on props and state
render
We can implement layer 2 using HOCs. But how can we implement layer 4?
Here I'm trying to get value from DefaultOpts.jsx and update the values to setState in Filters.jsx. But I'm getting error as below :
setState(...): Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.
Filters.jsx
import React from 'react';
import DefaultOpts from 'DefaultOpts.jsx';
export default class Filters extends React.Component {
constructor(props) {
super(props);
this.state = {
vOptions : []
}
this.handleOptions = this.handleOptions.bind(this)
}
handleOptions(params) {
console.log(params)
this.setState({
vOptions : params
});
}
componentDidMount() {
}
componentDidUpdate() {
}
render() {
return (
<div>
<DefaultOpts handleOptions={this.handleOptions.bind(this)} />
</div>
)
}
}
DefaultOpts.jsx
import React from 'react';
class DefaultOpts extends React.Component{
constructor(props) {
super(props);
}
componentDidMount() {
}
componentDidUpdate() {
}
render() {
var optArray = "";
$.ajax({
type: "get",
url: "url-path",
success: function(data) {
optArray = data;
}
});
return (
<div>
{this.props.handleOptions(optArray)}
</div>
)
}
}
export default DefaultOpts;
I got some answers in stackoverflow but I'm not able to get what's issue in my code. Please suggest me here what's wrong in my code..
You can't call this.props.handleOptions inside the render because it will trigger setState of the parent component - and you are still inside the rendering process. That's why it complains.
Try to execute this function inside the componentDidMount (together with your ajax call)
There are several problems with your code:
1) First and main one that results in the mentioned error is the fact that by calling handleOptions in render you are calling setState that in turn starts react life cycle. This is a really bad practice and always should/can be avoided.
2) You have one more async call to $.ajax in render that does not directly result in updating state but still considered a bad practice.
To conclude - your render function must not result in any app logic being performed, its task is to render results that have already been prepared. Do all heavy/async work in componentDidMount/componentDidUpdate and you will be fine.
render will execute before didMount... so you are setting the state before it is mounted
anyway move the $.ajax call to didMount, you shouldn't be doing logic things in render()
When i am trying to update the state of react component from an api call in componentwillmount function it is not working as expected. The value is not getting set.
export default class ListOfProducts extends Component {
componentWillMount() {
console.log('component currently mounting');
fetchOrder().then(data => {
console.log('order api call has been finished.', data);
this.setState(data, function() {
//this should print new data but i am still getting old data
console.log('current this.state.', this.state.orders)
})
})
}
constructor(props) {
super(props);
this.state = {
"orders": {
"order_items": []
}
};
}
render() {
let productList = [];
let productUnit = (
<View>
{this.state.orders.order_items.map(function(order,i){
return <ProductListItem
delivered={true}
productSKU={3}/>
})}
</View>
);
return productUnit;
}
}
If you want to perform any asynchronous requests, I suggest you perform them in the componentDidMount lifecycle method. This is the suggested path based on the Reactjs documentation for componentDidMount.
Invoked once, only on the client (not on the server), immediately
after the initial rendering occurs. At this point in the lifecycle,
you can access any refs to your children (e.g., to access the
underlying DOM representation). The componentDidMount() method of
child components is invoked before that of parent components.
If you want to integrate with other JavaScript frameworks, set timers
using setTimeout or setInterval, or send AJAX requests, perform those
operations in this method.
Perhaps you need to change:
" extends Component { "
to
" extends React.Component { "