I want to call a promise based function before dispatching an action to the store.
The problem is that I only want to call the function when the component is going to be displayed. I use a toggle action that turns the component on and off.
Here is a sample of my code:
if ( /*component is going to be displayed*/) {
init().then(function() {
store.dispatch(toggleSomething());
});
}
else {
store.dispatch(toggleSomething());
}
Action:
export const SomethingActions = {
TOGGLE_SOMETHING: 'TOGGLE_SOMETHING'
};
export function toggleSomething() {
return {
type: SomethingActions.TOGGLE_SOMETHING
};
}
Reducer:
export default function somethingState(state = defaultState, action) {
switch (action.type) {
case somethingActions.TOGGLE_SOMETHING
return Object.assign({}, state, { open: !state.open});
default:
return state;
}
}
part of the React component:
Something.propTypes = {
display: React.PropTypes.bool.isRequired
};
function mapStateToProps(state, ownProps) {
return {
display: state.something.open
};
}
I basically want to know the value of open/display of the component above or another way to know whether the component is being displayed or not.
I don't want to pollute the render function or store a bool that changes every time I call dispatch.
Is there a way to do that?
By the sounds of it, you'd want to take advantage of React's lifecycle methods. Particularly the componentWillMount and componentWillReceiveProps.
componentWillReceiveProps does not get triggered for the initial render, so you may want to extract out the logic into a separate function so that it can be reused for both hooks:
function trigger(isDisplayed) {
if (isDisplayed) {
init().then(function() {
store.dispatch(toggleSomething());
});
}
else {
store.dispatch(toggleSomething());
}
}
componentWillMount() {
trigger(this.props.display);
}
componentWillReceiveProps(nextProps) {
trigger(nextProps.display);
}
Q1: "The problem is that I only want to call the function when the component is going to be displayed"
A1: This is definitely a problem for react lifecycle methods, in particular, componentWillMount() & componentDidMount()
Q2: "I basically want to know the value of open/display of the component above or another way to know whether the component is being displayed or not."
A2: The componentDidMount() method will be called when the component is rendered. To prevent an infinite loop where the component calls your promise on render just to call the promise again when the state changes, avoid including the toggled state in your component. Dispatch actions on component mounting that toggle the state in the store, but don't use this state in this component. This way you know whether the component is rendered without having the UI update. I hope that helps!
import React from 'react';
class StackOverFlow extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
toggleSomethingOn();
}
componentWillUnmount() {
toggleSomethingOff();
}
render() {
return (
<div>
The component has been rendered!
<br />
</div>
);
}
}
function toggleSomethingOn() {
//dispatches action to toggle state "open"
}
function toggleSomethingOff() {
//dispatches action to toggle state "closed"
}
export default StackOverFlow;
A2: If you are just looking to find out if a component has been rendered (outside of your code) you could go to your browser's developer tools and search the elements/DOM for your component html.
Related
As in the title, I have a React component, the relevant part of which looks a bit like this as of now:
class myComponent {
fetchSomething = async() => {
this.setState({data: fetch(props.id)})
}
componentDidMount(){
this.fetchSomething()
}
render() {
{data}
}
}
const mapStateToProps = state => {
...
return { id }
}
export default connect(mapStateToProps)(myComponent);
I need to basically rerun the fetchSomething function whenever the id property in redux store changes, since I want to see the changes as soon as it happens, and I don't want to have to mount the component again.
you can try using componentDidUpdate lifecycle method.
componentDidUpdate(prevProps) {
if(prevProps.id !== this.props.id) {
this.fetchSomething();
}
}
More on componentDidUpdate or other useful lifecycle methods here
Lets say I have a component defined like this -
// actioncomponent.js
import React from 'react';
class ActionComponent extends React.Component {
state = {
isAction: false;
}
doAction = () => {
this.setState({isAction: true})
}
render () {
return (
<div>
Some render stuff..
</div>
)
}
}
export default ActionComponent
From another completely different file I want to set the state for the first component without rendering it in the new file so I need not use refs or props.
// newfile.js
import ActionComponent from './actioncomponent.js'
ActionComponent.doAction()
I'm aware the doAction can't be exported and calling it static doesn't have access to state either. How do I achieve something like this?
In React ecosystem you probably don't need this.
You can pass this method to a child component:
class ActionComponent extends React.Component {
state = {
isAction: false
}
doAction = () => {
this.setState({isAction: true})
}
render () {
return (
<div>
<Child doAction={this.doAction} />
</div>
)
}
}
And then in a Child component you can fire this action
// ...
render() {
<button onClick={() => props.doAction()}>Test</button>
}
If you need to fire action on parent, instead of child you might want to structure your state on upper level, or lift state up.
You can also achieve similar goal without drilling props, but you'll need some state management tool, e.g. Redux or in some cases Context API would be a great fit.
I have a complete running code, but it have a flaw. It is calling setState() from inside a render().
So, react throws the anti-pattern warning.
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
My logic is like this. In index.js parent component, i have code as below. The constructor() calls the graphs() with initial value, to display a graph. The user also have a form to specify the new value and submit the form. It runs the graphs() again with the new value and re-renders the graph.
import React, { Component } from 'react';
import FormComponent from './FormComponent';
import PieGraph from './PieGraph';
const initialval = '8998998998';
class Dist extends Component {
constructor() {
this.state = {
checkData: true,
theData: ''
};
this.graphs(initialval);
}
componentWillReceiveProps(nextProps) {
if (this.props.cost !== nextProps.cost) {
this.setState({
checkData: true
});
}
}
graphs(val) {
//Calls a redux action creator and goes through the redux process
this.props.init(val);
}
render() {
if (this.props.cost.length && this.state.checkData) {
const tmp = this.props.cost;
//some calculations
....
....
this.setState({
theData: tmp,
checkData: false
});
}
return (
<div>
<FormComponent onGpChange={recData => this.graphs(recData)} />
<PieGraph theData={this.state.theData} />
</div>
);
}
}
The FormComponent is an ordinary form with input field and a submit button like below. It sends the callback function to the Parent component, which triggers the graphs() and also componentWillReceiveProps.
handleFormSubmit = (e) => {
this.props.onGpChange(this.state.value);
e.preventdefaults();
}
The code is all working fine. Is there a better way to do it ? Without doing setState in render() ?
Never do setState in render. The reason you are not supposed to do that because for every setState your component will re render so doing setState in render will lead to infinite loop, which is not recommended.
checkData boolean variable is not needed. You can directly compare previous cost and current cost in componentWillReceiveProps, if they are not equal then assign cost to theData using setState. Refer below updated solution.
Also start using shouldComponentUpdate menthod in all statefull components to avoid unnecessary re-renderings. This is one best pratice and recommended method in every statefull component.
import React, { Component } from 'react';
import FormComponent from './FormComponent';
import PieGraph from './PieGraph';
const initialval = '8998998998';
class Dist extends Component {
constructor() {
this.state = {
theData: ''
};
this.graphs(initialval);
}
componentWillReceiveProps(nextProps) {
if (this.props.cost != nextProps.cost) {
this.setState({
theData: this.props.cost
});
}
}
shouldComponentUpdate(nextProps, nextState){
if(nextProps.cost !== this.props.cost){
return true;
}
return false;
}
graphs(val) {
//Calls a redux action creator and goes through the redux process
this.props.init(val);
}
render() {
return (
<div>
<FormComponent onGpChange={recData => this.graphs(recData)} />
{this.state.theData !== "" && <PieGraph theData={this.state.theData} />}
</div>
);
}
}
PS:- The above solution is for version React v15.
You should not use componentWillReceiveProps because in most recent versions it's UNSAFE and it won't work well with async rendering coming for React.
There are other ways!
static getDerivedStateFromProps(props, state)
getDerivedStateFromProps is invoked right before calling the render
method, both on the initial mount and on subsequent updates. It should
return an object to update the state, or null to update nothing.
So in your case
...component code
static getDerivedStateFromProps(props,state) {
if (this.props.cost == nextProps.cost) {
// null means no update to state
return null;
}
// return object to update the state
return { theData: this.props.cost };
}
... rest of code
You can also use memoization but in your case it's up to you to decide.
The link has one example where you can achieve the same result with memoization and getDerivedStateFromProps
For example updating a list (searching) after a prop changed
You could go from this:
static getDerivedStateFromProps(props, state) {
// Re-run the filter whenever the list array or filter text change.
// Note we need to store prevPropsList and prevFilterText to detect changes.
if (
props.list !== state.prevPropsList ||
state.prevFilterText !== state.filterText
) {
return {
prevPropsList: props.list,
prevFilterText: state.filterText,
filteredList: props.list.filter(item => item.text.includes(state.filterText))
};
}
return null;
}
to this:
import memoize from "memoize-one";
class Example extends Component {
// State only needs to hold the current filter text value:
state = { filterText: "" };
// Re-run the filter whenever the list array or filter text changes:
filter = memoize(
(list, filterText) => list.filter(item => item.text.includes(filterText))
);
handleChange = event => {
this.setState({ filterText: event.target.value });
};
render() {
// Calculate the latest filtered list. If these arguments haven't changed
// since the last render, `memoize-one` will reuse the last return value.
const filteredList = this.filter(this.props.list, this.state.filterText);
return (
<Fragment>
<input onChange={this.handleChange} value={this.state.filterText} />
<ul>{filteredList.map(item => <li key={item.id}>{item.text}</li>)}</ul>
</Fragment>
);
}
}
I am currently grabbing a prop from state and using it on an event listener. i.e.,
import * as React from 'react';
import { getDetails } from './actions';
interface Props {
selecting: boolean;
getDetails(): Action<void>;
}
#connect((state) => ({
selecting: state.items.selecting,
}), {
getDetails,
})
export default class Grid extends React.PureComponent<Props> {
onMouseEnter = () => {
if (!this.props.selecting) {
this.props.getDetails();
}
}
render() {
return (
<div onMouseEnter={this.onMouseEnter} />
);
}
}
However, whenever the selecting property changes, it causes a re-render to my component.
Is there a way to pass a variable from state through connect and NOT have it trigger this update to my component? I want it almost as if it were an instance-bound variable rather than a state variable.
Try overriding the shouldComponentUpdate() lifecycle function. This gives you much more granular control over when your component should or shouldn't re-render (at the cost of added code complexity).
shouldComponentUpdate(nextProps, nextState) {
if(nextProps.someLogic !== this.props.someLogic)
return false; // Don't re-render
return true;
}
Documentation: Here
Use shouldComponentUpdate() to let React know if a component’s output is not affected by the current change in state or props. The default behavior is to re-render on every state change, and in the vast majority of cases you should rely on the default behavior.
Imagine I have some "page" component, which needs to ask for data from a server. The data it requests will depend on whether or not the current user is authenticated. Further, in the event of a login, the page will want to reload the data. My question is, how can I accomplish something like this using HOCs rather than inheritance?
To illustrate the problem, I'll demonstrate a solution using inheritance. The program will have the following objects. I'll leave out the boilerplate code.
session: an EventEmitter that emits start when the session changes (either a login or a log out).
Page: the superclass that all pages inherit from
MyPage: the subclass of Page in this example
API: will be an API class for retrieving data from the server
Here's the code:
// Page superclass
class Page extends React.Component {
componentWillMount() {
session.on("start", this.loadData);
this.loadData();
}
loadData() {
// this method is overwritten in subclasses
}
}
// MyPage subclass
class MyPage extends Page {
loadData() {
if(session.isAuthenticated()) {
API.loadPrivateData();
} else {
API.loadPublicData();
}
}
}
Here's a solution that uses an HOC, but seems less elegant than inheritance. It still requires that every "subclass" page have a method loadData, and it requires that method to be called in every "subclass's" componentWillMount.
// Page HOC
function Page(WrappedComponent) {
return class EnhancedPage extends React.Component {
componentWillMount() {
session.on("start", this.loadData);
// this._page.loadData() will fail here
// since this._page is undefined until rendering finishes
}
loadData() {
this._page.loadData();
}
render() {
return <WrappedComponent {...props} ref={(e) => { this._page = e; }} />
}
}
}
// MyPage
class MyPage extends React.Component {
componentWillMount() {
this.loadData();
}
loadData() {
if(session.isAuthenticated()) {
API.loadPrivateData();
} else {
API.loadPublicData();
}
}
}
const component = Page(MyPage)
// what would make sense here is to have a method something like
// const component = Page(MyPage, () => MyPage.loadData())
// but then the MyPage.loadData logic would need to be defined
// elsewhere
This pattern will happen often: I'll want to load some data, then reload when the session changes. I'd like to understand the "react" way of accomplishing the same.
EDIT: I am not trying to pass a username or "loggedIn" flag through the HOC. That is to say something like <WrappedComponent isLoggedIn={session.isAuthenticated()} {...props} /> won't cut it here. Tying the API logic to props requires that I check for changes in MyPage.componentWillUpdate().
When using a HOC you shouldn't place the loadData function on the wrapped component. Instead pass the function as a parameter to the HOC constructor.
Something like this might work for you. The sessionHoc function takes a callback function which'll be called every time the session state changes. Its result will be passed to WrappedComponent as a data prop.
function sessionHoc(onSessionChange) {
return function (WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null,
};
session.on('start', this.handleSessionChange.bind(this));
}
handleSessionChange() {
this.setState({
data: onSessionChange(),
});
}
render() {
return <WrappedComponent data={data} {...this.props} />
}
};
};
}
class MyPage extends React.Component {
render() {
// Just access this.props.data here!
}
}
const EnhancedPage = sessionHoc(function () {
if (session.isAuthenticated()) {
return API.loadPrivateData();
} else {
return API.loadPublicData();
}
})(MyPage);
Hopefully this helped! :)