I have a login and register component with forms that were created a while ago before these methods were deprecated, I've been looking around but cannot seem to find a solution, how would I go about refactoring this to using getDerivedStateFromProps?
componentWillReceiveProps(nextProps) {
if (nextProps.auth.isAuthenticated) {
this.props.history.push("/dashboard")
}
if (nextProps.errors) {
this.setState({
errors: nextProps.errors
});
}
The answer to the question you asked is probably not going to be satisfactory. :-) The answer is that if you really need to derive state from props (you probably don't, just use props.errors directly in render), you do it with the newer getDerivedStateFromProps static method that accepts props and state and (potentially) returns a state update to apply:
static getDerivedStateFromProps(props, state) {
return props.errors ? {errors: props.errors} : null;
}
or with destructuring and without the unused state parameter:
static getDerivedStateFromProps(({errors})) {
return errors ? {errors} : null;
}
But, you're saying "But that doesn't do the authentication thing...?" That's right, it doesn't, because that componentWillReceiveProps shouldn't have, either, it violates the rule props are read-only. So that part shouldn't be there. Instead, if that entry in props.history is supposed to be there, it should be put there by the parent component.
Since you're using componentWillReceiveProps to keep a local state in sync with props you have two alternatives:
Declare your initial state based on props and use componentDidUpdate to ensure props synchronicity
class Component extends React.Component{
state = { foo : this.props.foo }
componentDidUpdate(prevProps){
if(this.props.foo !== prevProps.foo)
this.setState({ foo : prevProps.foo })
}
}
This is actually triggering an extra render everytime, if you have some local state that is always equal to some prop you can use the prop directly instead.
Use getDerivedStateFromProps to update the state based on a prop change, but keep in mind that you probably don't need to use it
class Component extends React.Component{
static getDerivedStateFromProps(props){
return { foo : props.foo }
}
}
Related
I manage scrolling in my component with a ref that is the current scroll value. I cannot use state and setState, because when the user would be scrolling the setState method would make things really choppy and impossible. Hence, I decided to use refs that don't re render the component, but save the value when the component re renders.
My problem is that I cannot set an initial value for the ref. As I am using a class component I cannot use the simple useRef react hook that allows the initial value to be set easily... instead I use React.createRef() but when I put the value in the parenthesis () it doesn't seem to register it and is undefined until the user scrolls.
How can I fix that?
Here when the component updates (for example when a new message is sent), I want to make sure that it scrolls down to the newest message only if the user is not browsing older messages somewhere above.
To set the initial value of a ref created by React.createRef, do something like the following:
constructor(props) {
super(props):
this.scrollPosition = React.createRef();
this.scrollPosition.current = 201;
}
I do want to mention though, that since you're in a class component, you probably don't need a ref. In function components, refs are often used the way you're using them: to have a mutable object which persists from render to render.
But in class components, you already have a mutable object which persists from render to render: this. So you can probably ditch the ref and just do:
constructor(props) {
super(props);
this.scrollPosition = 201;
}
componentDidUpdate(prevProps, prevState) {
if (this.state.message === "" && this.scrollPosition > 200) {
// ...
}
}
I don't know why createRef doesn't take an initial value, but I wrote a replacement for it:
export function createRef<T>(initialValue: T): {current: T};
export function createRef<T>(): {current: T|null};
export function createRef<T>(initialValue?: T): {current: T|null} {
return {current: initialValue ?? null}
}
I just want to understand why in the application I have following situation, below is my constructor of class component:
constructor(props) {
super(props);
this.state = {
tableAlerts: props.householdAlerts,
initialAlerts: props.householdAlerts
}
console.log('householdAlerts', props.householdAlerts)
}
in render function I have:
const { householdAlerts } = this.props;
My issue is that in constructor I got empty array, but in render funtion I have the data. Is it possible to get the data in constructor?
This is a very bad pattern when using the class component. You are ignoring any props updates when you copy the value into state. to manage it:
It requires you to manage two sources of data for the same variable: state and props. Thus, you need to add another render each time your prop change by setting it into state (don't forget to test on equality from prev and next values to avoid being in an infinite loop).
You can avoid setting the state each time your props change by using the getderivedstatefromprops lifecycle method.
So the recommendation is: just use the props; do not copy props into state.
To learn more why you shouldn't, I highly recommend this article.
It is not recommended to set your initial component state in the constructor like so because you gonna lose the ability to use { setState } method after to update this property/state.
The best practice is indeed to refer directly to the prop with { this.prop.householdAlerts }, and keep the state usage for local (or in child components} cases.
if anyhow you want to store props in component state for some reason, call it in lifeCycle -
componentDidMount() {
const { tableAlerts, initialAlerts } = this.props;
this.setState({ tableAlerts, initialAlerts });
}
Hagai Harari is right. Nevertheless, your actual problem seems to be that during your initial rendering the array is empty. Can you ensure that the array has some items, when your component is rendered for the first time?
First rendering -> calls constructor
<YourComponent householdAlerts={[]} />
Second rendering -> updates component
<YourComponent householdAlerts={[alert1, alert2, alert3]} />
If you want initial state to have the prop value.Try something like this with 'this' keyword
constructor(props) {
super(props);
this.state = {
tableAlerts: this.props.householdAlerts,
initialAlerts: this.props.householdAlerts
}
console.log('householdAlerts', props.householdAlerts)
}
I think it works differently but I don't know how it works.
1. Using class variable
export default class Test extends Component {
constructor() {
this.active = false;
}
render() {
this.active = this.props.name === 'Dan'? true : false;
return (
<div>
{this.active? 'ssup?' : 'noooo'}
</div>
);
}
}
2. Using React component state
export default class Test extends Component {
constructor() {
this.state = { active: false };
}
render() {
if(this.props.name === 'Dan') {
this.setState({active: true});
}
return (
<div>
{this.active? 'ssup?' : 'noooo'}
</div>
);
}
}
I think it doesn't need to re-render using State if it's only affected by received props.
The difference between the two is that React will re-render your component when state changes (with this.setState(/*...*/)).
If you update the class variable, React will be unaware of it and won't re-render your component.
Note that what you're achieving in your code requires neither state or class variable. You're simply computing another value directly from the props. A better way to write your component would be like this :
export default class Test extends Component {
render() {
const active = this.props.name === 'Dan';
return (
<div>
{active? 'ssup?' : 'noooo'}
</div>
);
}
}
The simple answer to your question is that by using state you call the setState() which automatically calls render() automatically. Which cannot be obtained by class variables
You use the `state variables` when you want to change the component when that variable is changed.
When you don't want to automatically call `render()` you use the `class` variables
React component only re-renders when there are changes to state or class. But updating class variable involves neither so it does not trigger render.
Though using state may seem similar to class variable but state is a protected keyword in React that refers to stored component data. The major difference between using class variables and state is updating data. Instead of manually reassigning the variable, you call this.setState().
When you call this.setState(). It compares this new state to the previous state. If there is a change, React re-renders the component, resulting in the updated count value displayed on the screen.
But when you update class variable, it sure gets updated but does no re-render. You can do so using this.forceUpdate(). But Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render().
Refer to this article for detailed info.
I want my component know if some library is already loaded. To know that from any context i connect it to the "library" reducer of my store to my component.
I also pass it a configuration object this.props.dataObject from the parent where the component has been called. Like this:
class GoogleButton extends Component {
render() {
if (this.props.libraries.google) {
return <a id='sharePost' className='google_icon'></a>
} else {
return null
}
}
componentDidUpdate() {
gapi.interactivepost.render('sharePost', this.props.dataObject)
}
}
function mapStateToProps(state) {
return { libraries: state.libraries }
}
export default connect(mapStateToProps)(GoogleButton)
The reducer that handles the libraries state is like this:
let newState = {...state}
newState[action.libraryName] = action.state
return newState
When I change the library state componentDidUpdate works. The problem is when i change the prop inherited by the parent this.props.dataObject. In that case is where componentDidUpdate wont fire. If i remove the connect from the component it works as espected. I'm missing something here?
Most likely some of your props are mutated outside the component.
For example, you might be rendering your component like this:
class Parent extends Component {
constructor() {
super()
this.state = { libraries: {} }
}
handleClick() {
// MUTATION!
this.state.libraries.google = true
// Normally this forces to update component anyway,
// but React Redux will assume you never mutate
// for performance reasons.
this.setState({ libraries: this.state.libraries })
}
render() {
return (
<div onClick={() => this.handleClick()}>
<GoogleButton libraries={this.state.libraries} />
</div>
)
}
}
Because Redux apps deal with immutable data, connect() uses shallow equality check for its props to avoid unnecessary re-renders. However, this won’t work if you use mutation in your app.
You have two options:
Don’t Mutate Anything
This is the best option. For example, instead of something like
handleClick() {
this.state.libraries.google = true
this.setState({ libraries: this.state.libraries })
}
you can write
handleClick() {
this.setState({
libraries: {
...this.state.libraries,
google: true
}
})
}
This way we are creating a new object so connect() wouldn’t ignore the changed reference. (I’m using the object spread syntax in this snippet.)
Disable Performance Optimizations
A worse alternative is to completely disable performance optimizations made by connect(). Then your props would update even if you mutate them in the parent, but your app will be slower. To do this, replace
export default connect(mapStateToProps)(GoogleButton)
with
export default connect(mapStateToProps, null, null, { pure: false })(GoogleButton)
Don’t do this unless absolutely necessary.
I solved it. I'm not 100% sure that this is accurate, but I will explain. If im wrong with something, please correct me.
I keep thinking about the shallow equality check that Dan said in his answer. The problem was there.
I was passing down an object from the parent and the nested elements of that object were the ones that changed. The object remain the same. So with the shallow equality check that connect brings the component will never update.
My solution was in the parent use Object.assign({}, dataObject) when I pass down the prop so I make another different object. Now shallow equality check could compare it and determinate that the props have changed and there before update the component.
i had same problem and i used object.assign for create new state but i use combineReducer and it cause multi level state ,in my case i pass whole state as props to component so shallow equality check can not detect my state change so componentDidUpdate didnot call,it is important to pass state in level it change when using combine reducer
in my case i pass it like this
const MapStateToProps=(state)=>{
return {
reportConfig:state.ReportReducer
}
};
and my state tree is like this
{
ReportReducer: {
reportConfig: {
reportDateFilter: 'this week',
reportType: null,
reportShopId: null,
updateShop: true
}
}
}
and in my reducer and return it like this as ReportReducer
export default combineReducers({reportConfig});
and my root reducer is like this
const rootReducer =combineReducers({ReportReducer});
const store = createStore(rootReducer ,{},enhancer);
Another option that you can use is to make a deep copy of the inherit prop this.props.dataObject on the child component, this in order for the componentDidUpdate to 'catch' the updated prop, you could use:
dataObject={JSON.parse(JSON.stringify(valueToPass))}
Use this where you are passing the prop from the parent component, this works for me in a similar problem (This applies when you don't have any function inside the prop).
I had this exact same problem with Components I used from an external library.
So I didn't had the option to modify the inherited property.
I only needed a part of the inherited property object (will use dataObject for simplicity). Solved it by adding it to the mapStateToProps function:
function mapStateToProps(state, ownProps) {
return { libraries: state.libraries, neededValue: ownProps.dataObject.value }
}
By which a shallow compare is enough to notice a value change. So use this.props.neededValue iso this.props.dataObject.value in the render() function.
Is it possible (or even a good idea) to add my own props to another React component, like:
<SomeCustomComponent myOwnParam={handler}>
As mentioned by Tyrsius, it really depends on the implementation of SomeCustomComponent. If the component does not use the myOwnParam prop anywhere, passing it won't accomplish anything. On the other hand, some React components might use JSX spread attributes to reference props not directly enumerated in the code.
As an example, the following implementation of SomeCustomComponent would pass your myOwnParam prop down to its child div:
class SomeCustomComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
var {customComponentProp, ...other } = this.props;
return (
<div customComponentProp={customComponentProp} {...other}></div>
);
}
}
So again, it depends on the implementation of SomeCustomComponent what will happen.
See Transferring Props documentation for more details: https://facebook.github.io/react/docs/transferring-props.html
This won't cause an error, but unless SomeCustomComponent is looking for this prop nothing will be done with it. It is possible for a component to loop over its props, so this could be a usable strategy, but I am not sure what you would do with it. You couldn't define anything but iteration logic over properties that you don't know in advance.