How do I call a component function inside static getDerivedStateFromProps?
This is my code
class Box extends Component {
updateVariables() {
this.statColorClass = "no-change";
if(this.props.dir === "up") {
this.statColorClass = "up";
this.dirIcon = <GoArrowUp />;
} else if (this.props.dir === "down") {
this.statColorClass = "down";
this.dirIcon = <GoArrowDown />;
}
}
static getDerivedStateFromProps() {
this.updateVariables(); // not working
}
render() {}
}
The line this.updateVariables(); is not working. How do I call updateVariables() ?
this cannot be accessed inside static functions, that is by design. Still if you want to get this inside getDerivedStateFromProps, you could use the below code, but you should never use like this.
constructor() {
super();
this.state = {
currentInstance: this
}
}
Now inside your getDerivedStateFromProps() access currentInstance which is same as this
static getDerivedStateFromProps(props, state) {
state.currentInstance.updateVariables();
}
getDerivedStateFromProps is intended to update component state from received props. It was deliberately designed to not allow access to component instance.
statColorClass, etc. properties represent component state, but they aren't parts of state, this is rarely needed in React stateful components. A proper solution would be to refactor component to use state:
static getDerivedStateFromProps(props, state) {
state = {...state, statColorClass: "no-change" };
if (props.dir === "up") {
state.statColorClass = "up";
state.dirIcon = <GoArrowUp />;
} else if (props.dir === "down") {
state.statColorClass = "down";
state.dirIcon = <GoArrowDown />;
}
return state;
}
This method doesn’t have access to the component instance. If you want to reuse functionality then you could create pure components outside the class. The whole point of this function is to convert props to new state, which is what the static function should return. It is not intended to cause side effects. Not sure why you are using instance vars to handle state.
Related
I have a reactcomponent that has a few obsolete events:
componentWillMount() {
const { applicationStages } = this.props;
if (applicationStages && applicationStages.length > 0) {
this.setState({
appColumnsSorted: this.getSortedAppColumns(someVar),
});
}
}
componentWillReceiveProps(nextProps) {
const {
presets: { sortCriteria: sortBy, customCriteria },
} = nextProps;
const { appColumnsSorted } = this.state;
const sortedColumnsUpdated = this.getSortedAppColumns(
appColumnsSorted,
sortBy,
true
);
this.setState({
appColumnsSorted: sortedColumnsUpdated,
});
}
getSortedAppColumns = (appColumns, sortBy, criticalFirst) => {
//returns object
};
'componentWillMount' is basically to initialize the appColumnsSorted. The issue is that with v16 this event is obsolete. So what can event can I use for this now? Also what is the way to migrate 'componentWillReceiveProps' in this scenario?
What you're using componentWillMount for can be done in the constructor so
componentWillMount() {
const { applicationStages } = this.props;
if (applicationStages && applicationStages.length > 0) {
this.setState({
appColumnsSorted: this.getSortedAppColumns(someVar),
});
}
}
will change to
export default class YourClass extends Component {
constructor(props) {
// keep a separate method just to avoid writing code in constructor for readability
this.state = constructInitialState(props);
}
constructInitialState(props) {
const state={};
//More state handling as required
const { applicationStages } = props;
if (applicationStages && applicationStages.length > 0) {
state.appColumnsSorted = this.getSortedAppColumns(someVar);
}
return state;
}
}
This approach is slightly better because getDerivedStateFromProps will be called before each render and will waste computation.
From the code snippet it is not obvious why you want to store it in state. If you do save it to state then the only way you have would be to use componentDidUpdate as mentioned in the other answer by Aaditya Thakkar. This will require you to mirror your props in state only for comparison purpose (Mapping props to state is not the best way, more on this link https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#anti-pattern-unconditionally-copying-props-to-state)
I would, however, recommend calling your sort computation method and return its result in render directly; this would avoid extra checks in componentDidUpdate to render the second time. As I'm not aware if these props are coming from redux or a parent React component, another option would be to avoid expensive computation in this class and simply provide the correct value from either the parent component or calculate the value in the redux store and send the final prop directly for use.
ComponentWillReceiveProps can be replaced with getDerivedStateFromProps. 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. It's a static method, so this can not be used inside it.
Hence, you can no longer reference this.getSortedAppColumns from getDerivedStateToProps, you need componentDidUpdate lifecycle for that. Here, I have broken down ComponentWillReceiveProps into getDerivedStateFromProps and componentDidUpdate:
static getDerivedStateFromProps(nextProps, prevState) {
const {
presets: { sortCriteria: sortBy },
} = nextProps;
if (sortBy === prevState.sortBy) return null;
return ({ sortBy: nextProps.sortBy });
}
componentDidUpdate(_, prevState) {
const { appColumnsSorted, sortBy } = this.state;
if (sortBy !== prevState.sortBy) {
const sortedColumnsUpdated = this.getSortedAppColumns(
appColumnsSorted,
sortBy,
true
);
this.setState({
appColumnsSorted: sortedColumnsUpdated,
});
}
}
Suppose I have a container component for handling app logic which has a lot of methods:
class ScreenContainer extends React.Component
{
state = {
inputs: { /* various properties for input values */ },
thingyActive: false,
someList: ["thing1", "thing2"],
// ...etc.
};
handleInputChange = e => {
const { name, value } = e.target;
this.setState(prevState => ({
inputs: { ...prevState.inputs, [name]: value }
}));
};
toggleThingy = () => this.setState(prevState => ({
thingyActive: !prevState.thingyActive
}));
coolMethod = () => { /* Do cool stuff */ };
boringMethod = () => { /* Do boring stuff */ };
// ...more methods...
}
I need ALL of these methods to be accessible to inner components. I'll use a Context provider in this example, and we'll just say that the context gets consumed by various nested presentational components making up a screen in the application.
const ScreenContext = React.createContext();
To pass methods either down to a child component or into a context provider value, it seems you always end up having to do something like below (note that I'm lifting the "actions" into state in this example per the advice given in the React documentation).
class ScreenContainer extends React.Component
{
constructor()
{
super();
this.state = {
// ...same state as before, plus:
actions: {
handleInputChange: this.handleInputChange,
toggleThingy: this.toggleThingy,
coolMethod: this.coolMethod,
boringMethod: this.boringMethod,
everySingleOtherMethod: this.everySingleOtherMethod,
// ...on and on
}
};
}
// ...same methods as before...
render()
{
return (
<ScreenContext.Provider value={this.state}>
{this.props.children}
</ScreenContext.Provider>
);
}
I was looking for a way to avoid passing them all one by one. A possible solution I found involves using a getter and looping through the class instance properties like so:
get allMethods()
{
let output = {};
for (var prop in this)
{
if (this.hasOwnProperty(prop) && typeof this[prop] === "function")
output[prop] = this[prop];
}
return output;
}
Then I can just do:
// (in ScreenContainer constructor)
this.state = {
// ...state,
actions: this.allMethods
};
The getter code could also be extracted out into a utility function for reuse in other container-type components if needed. Obviously, this is only worthwhile if there are a ton of methods to be passed down.
It seems simple enough and appears to work just fine as long as it's done in the contructor. Is there anything crazy about this? Is it bad practice in any way, or does it have any potential side effects I'm not aware of? Is there maybe a better way I'm missing?
EDIT
I've updated the example to be closer to my real code; it now shows what kinds of things the methods might do and uses a Context setup rather than passing the methods down as props to a single child component.
If a class doesn't maintain a state, and class methods are supposed to be used separately as helper functions, they shouldn't be a part of the class, let alone class component. A class acts as namespace in this case. In modern JavaScript, modules are used as namespaces. It can be:
export const coolMethod = () => { /* Do cool stuff */ };
export const coolerMethod = () => { /* Do even cooler stuff */ };
export const boringMethod = () => { /* Do boring but necessary stuff */ };
ScreenContainer component is an example of 'smart' container component. It's always preferable to list passed functions explicitly rather than pass them all automatically. ScreenContainer may get private methods at some point. And there should be a guarantee that lifecycle hooks won't be passed accidentally, too.
If it is supposed to have a single child, it can be applied as higher-order component:
const withScreen(Comp) => {
return class ScreenContainer extends React.Component {
...
render() {
return <Comp handleInputChange={this.handleInputChange} /* ... */ />;
}
}
}
In this particular case render can be distinguished from passed functions because the latter are instance methods (arrow functions). While this kind of magic generally isn't recommended because it may cause problems and won't work properly for private methods, it can be shortened to:
render() {
const fns = {};
for (const method of Object.keys(this)) {
if (typeof this[method] === 'function')
fns[method] = this[method];
}
return <Comp {...fns} {...this.props} />;
}
For multiple children, ScreenContainer children could be traversed to add props in a similar way.
For indirect children, context API can be used to pass functions.
While it's possible to pass ScreenContainer this to children, this isn't recommended because this breaks the encapsulation and contradicts the principle of least privilege.
One way I've done this is to instantiate a new instance in the constructor of the child component like this:
class ChildComponent extends Component {
constructor(props) {
super(props);
this.Container = new MyContainer();
}
Then you can use any methods like:
this.Container.coolMethod()
Edit
I misunderstood. I've only done this by creating a helper class that you instantiate, not a component. It is helpful when you have methods you want to use in multiple components without having to pass all your methods as props through the component tree.
I'm trying to make some component which data output depends on some external API.
So I have this snippet:
class Parent extends Component {
constructor(props) {
super(props)
this.state = {
somethingFromAPI: ''
}
}
componentDidMount() {
/*
something on axios.get() which updates this.state.somethingFromAPI
which normally can have some time delay till executed
*/
}
render() {
return (
<Child value={this.state.somethingFromAPI} />
)
}
}
class Child extends Component {
constructor(props) {
super(props)
this.state = {
value: this.props.value || ''
}
}
handleChange(event) {
this.setState({
value: event.target.value
})
}
static getDerivedStateFromProps(props, state) {
// if difference
return {
value: props.value
}
}
render() {
return (
<div>
<input value={this.state.value} onChange={this.handleChange.bind(this)} />
</div>
)
}
}
ReactDOM.render(
<Parent />
document.getElementById('app')
);
Seems like this works fine, initializing component, and getting API data, after that, input value seems to be updated, which is what I expect.
Problem that hurts me a lot is if I type something inside input, that will call handleChange, but will also trigger this getDerivedStateFromProps and will replace newer inputed value with that "old" from API.
Is this good way of doing this, maybe I made mistake at start with understanding of how it should be done? Guide me in right direction.
I'm yet pretty new to React.
Generally, need to make form which I can use for new input, or updating existing data (like some posts, etc.), so I can load API data.
Best regards.
Did you consider using shouldComponentUpdate instead if using getDerivedStateFromProps
something like this may solve your problem:
shouldComponentUpdate(nextProps, nextState) {
const { value: nextPropsValue } = nextProps;
const { value: propsValue } = this.props;
const { value } = this.state;
if (nextPropsValue !== propsValue && nextPropsValue !== value) {
this.setState({
value: nextPropsValue
});
}
return value !== nextState.value;
}
Update the answer adding comparison with current props value
I think using getDerivedStateFromProps here may be unnecessary. If you want to prevent a render in certain cases, consider using shouldComponentUpdate https://reactjs.org/docs/react-component.html#shouldcomponentupdate. But it sounds like you basically just need to use your input change handler to keep the state of the input, which you're already doing.
You should also check this article out on why someone shouldn't use getDerivedStateFromProps. It's very informative.
I have a couple of React components that are all based of the same base class, these component have a couple of properties which I would like to read before I render the component. This has to do with some conditions that are used somewhere else.
Currently I am calling a method, with something like this in my Render function.
public getWidget(): JSX.Element {
let widget = null;
switch (widgetType) {
case 'widget1': {
widgetComponent = new Widget1(props); // private variable in my class
widget = (<Widget1 { ...props } ref = { some-ref });
}
case 'widget2': {
widgetComponent = new Widget2(props); // private variable in my class
widget = (<Widget2 { ...props } ref = { some-ref });
}
}
return widget;
}
This way I can ask the widget some stuff about it's default values and render the widget variable in my Render function, like this:
render() {
const widget = this.getWidget();
const somethingIWantToKnow = this.widgetComponent.someProperty;
return ({ widget });
}
From what I understand, the reference I set for my React Component is only available after I render? Otherwise I could just use that.
I also tried calling this.widgetComponent.render() in my own Render method, but this does not set up the component correctly (probably because of missing componentWillMount and componentDidMount calls.
I just can't believe this is the way to go, is there a way to render from this.widgetComponent in my Render method, or is there a way to get properties from the class behind a JSX.Element?
NULL checks and other stuff is all removed from these code snippets :)
Give your widget a ref,
widget = (<Widget1 { ...props } ref = { widget1 } />);
Then you can access your instantiated component in componentDidMount and use the ref to access the property,
componentDidMount(){
const somethingIWantToKnow = this.widget1.current.someProperty
}
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