1

I have already read all the stack-overflow questions related to this problem, also this official react post and the preferred solutions.
It's not recommended to use componentWillReceiveProps anymore!

Before you mark this question as duplicate, please understand my specific question, I didn't see any solution for my specific problem.

What I'm trying to do is very simple:
I have component KInputRange that received the value from props and send the value out (callback) onEnter event (will send the value to server only on enter) The props.value can randomly change (coming by websocket from the server)

My Question:
Inside my components, the <input> value attribute will get the data from props or from state?

If from props: How can I update the value internally when the user type input data?

If from state: How can I update the new value if the props.value has change randomly from the server?

I'm actually need to update my internal state on props change but how to do it today, if react says that's anti-pattern?

This my code so far:

class KInputRange extends React.Component<any, any> {
    constructor(props: any) {
        super(props);
    }

    private onKeyDown(e: any): void {
        //If the key is enter call to props.onEnter with the current value

    }

    private onChange(e: any): void {
        //if user change the value- change the internal value
    }

    public render() {
        return (
            <input value={?????} type="text" onChange={(e) => this.onChange(e)} onKeyDown={(e) => this.onKeyDown(e)}/>
        );
    }
}

Usage:

   <KInputRange value={this.state.dataFromTheServer}   onEnter={(val: number) => this.kInputRangeEnterClicked(val)}/>
cheziHoyzer
  • 4,803
  • 12
  • 54
  • 81
  • Have you tried using getDerivedStateFromProps , You can check if props has been changed and if so you can derive the updated state. – Atul Apr 09 '19 at 00:06
  • According to official document you will use it only in rare cases, and my case is very trivial, also static is not good, I need "this" to make extra more logic. – cheziHoyzer Apr 15 '19 at 10:51

5 Answers5

1

You can use a function component as mentioned in the post you linked here. To update the value internally you can use React's State Hook.

Something like this:

import React, { useState } from 'react';

const KInputRange = (props) => {

    const [value, setValue] = useState(props.value);

    function onKeyDown(e: any): void {
        //If the key is enter call to props.onEnter with the current value
    }

    function onChange(e: any): void {
        setValue(e.target.value);
    }

    return (
            <input value={value} type="text" onChange={(e) => this.onChange(e)} onKeyDown={(e) => this.onKeyDown(e)}/>
    );
}
neomib
  • 3,503
  • 4
  • 17
  • 27
1

First, as @Atul said, you DO need to use getDerivedStateFromProps. It's all because you need to control your component value depending on both - props and internal state. Assuming you using flow this code should help:

// @flow
import * as React from "react";

type Properties = {
    remoteValue: string,
    onSubmit: (value: string) => void
};

type State = {
    remoteValueMemo: string,
    internalValue: string
};

class KInputRange extends React.Component<Properties, State> {

    static defaultProps = {
        remoteValue: "",
        onSubmit: () => {}
    };

    state = {
        remoteValueMemo: this.props.remoteValue,
        internalValue: this.props.remoteValue
    };

    static getDerivedStateFromProps(props: Properties, state: State) {
        if (state.remoteValueMemo !== props.remoteValue) {
            return {
                remoteValueMemo: props.remoteValue,
                internalValue: props.remoteValue};
        }
        return null;
    }

    handleValueChange = (event: SyntheticEvent<HTMLInputElement>) => {
        this.setState({internalValue: event.currentTarget.value});
    };

    handleKeyDown = (event: SyntheticKeyboardEvent<HTMLInputElement>) => {
        if (event.keyCode === 13) {
            this.props.onSubmit(this.state.internalValue);
        }
    };

    render(): React.Node {
        const {internalValue} = this.state;

        return (
            <input value={internalValue} onChange={this.handleValueChange} onKeyDown={this.handleKeyDown}/>
        );
    }
}

export default KInputRange;
Bender
  • 617
  • 6
  • 17
  • I know it @Bender, and I also try to use it, after I saw the official doc to not use it (only for rare cases) I understand that my very trivial case is not so trivial for React, Also I don't have an access to "this" (it's static) and I want to change my internal private class member on props change, so it's not good solution for me. – cheziHoyzer Apr 16 '19 at 13:58
  • It's the only and "ideomatic react" way to reach your requirements (at least for now). There is nothing special to do with `static` it's only used to derived new state depending on component tracking prop changes. If you also want to implement some "side effect" when props are changed you shoul use `componentDidUpdate` – Bender Apr 16 '19 at 14:49
1

The argument passed to useState is not used to update the state on re-render.

You should use the useEffect Hook.

import React, { useEffect, useState } from 'react';

const Test = (props) => {

    const [value, setValue] = React.useState({...props.value});

    useEffect(() => {
        setValue(props.value);
    }, [props.value])
  
    //...
}

Similar topic here: React.useState does not reload state from props

programort
  • 140
  • 5
0

You can use useEffect hook of react whenever you need to do something on specific props change in function component

useEffect(() => {
    // You can place your logic here to run whenever value changes 
},[value]);

[value] is a dependency so that whenever the value changes you useEffect hook calls

Hope above is helpful to you.

  • `componentWillReceiveProps()` considered legacy and will be removed in React 17* you sould avoid using it https://reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops – Bender Apr 16 '19 at 09:50
0

A little late but hope this helps someone. Since you're deciding to render the input based on either props or state, what you need to do use "ComponentDidUpdate(prevProps,prevState)" to compare the prev props with current props. Your input component should be set based on state in your constructor or "OnChange" handler, but if you receive new props, then you should update your state. I.E

onChange(){
 this.setState({
     value : // new value
 })
}

componentDidUpdate(prevProps, prevState) {
if (this.props.value !== prevProps.value ) {
            this.setState({
                value : this.props.value 
            })
        }
}