2

I am learning react hence this could be primitive problem but after lot of effort and searching I could not get this to work. Any help really appreciated.

Requirement

I have a html form which is filed by API response after user clicking a button. After API response user should be able to change the filled data.

Problem From redux i set the properties from api call. This information set to state which uses to render to input field. when user types the data in input filed i cannot seems to change the state without causing infinite loop or not updating state at all.

This is my current work

class Counter extends Component {
    constructor(props) {
        super(props);
        this.state = {
            title: 'Initial title'
        }
    }
    componentDidUpdate(prevProps, prevState, snapshot) {
        if (this.props.hasOwnProperty('todoInfo')) {
            const {title} = this.props.todoInfo;
            if (title !== prevState.title) {
                this.setState({
                    title: title
                });
            }
        }
    }
    handleChange = (event) => {
        const {name, value} = event.target;
        this.setState((prevState) => ({
                ...prevState,
                title: value
        }));
    }
    render() {
        const {title} = this.state;
        return (
            <div>
                <p>
                    <button onClick={() => this.props.fetchData()}> fetch data</button>
                </p>
                <p>
                    <input type="text" id="fname" name="fname" value={this.state.title}
                           onChange={this.handleChange}></input>
                </p>
            </div>
        );
    }
}
const mapStateToProps = (state) => {
    console.log('map state to props ' + JSON.stringify(state));
    return {
        todoInfo: state.todoReducers.todoInfo
    };
};

const mapDispatchToProps = (dispatch) => ({
    fetchData: () =>
        dispatch(fetchData())
});

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

actions

export const fetchData = () => {
    return (dispatch) => {
        axios.get('https://jsonplaceholder.typicode.com/todos/1')
            .then(response => {
                const todoInfo = response.data;
                dispatch({
                    type: GET_TODO,
                    payload: todoInfo
                })
            })
            .catch(error => {
                const errorMsg = error.message;
                console.log(errorMsg);
            });
    };
};

reducer

const todoReducers = (state  = {}, action) =>{
    switch (action.type) {
        case GET_TODO:
            console.log('here ' + JSON.stringify(action));
            return {
                ...state,
                todoInfo: action.payload
            };
        default:
            return state;
    }
}


export default todoReducers;

I want my input text field load data from API then update with any user input. If they click fetch data again then user inputs are replaced by API fetch data. how to do this ?

EDIT Solution according to accepted answer can be found https://github.com/mayuraviraj/react-redux-my-test

3
  • 1
    Your handleChange function should send user changes not only to the local state, but also to Redux (similar to fetchData) Commented Jul 16, 2021 at 8:05
  • @MaxAlex can you give example how to do that ? Commented Jul 16, 2021 at 8:17
  • 1
    Viraj pay attention to the answer of Robert Tirta, it is generally more correct, although it contains errors. He recommends that you store the state only in Redux state. Commented Jul 16, 2021 at 8:38

3 Answers 3

1

If your plan is to use redux, then you are trying to use a centralised state, don't mix it with the local state. (although it depends on your use case really)

Instead of your current code, remove the componentDidUpdate

class Counter extends Component {
    constructor(props) {
        super(props);
    }
   
    handleChange = (event) => {
        const {name, value} = event.target;
        this.props.changeTitle(value);
    }
    render() {
        const {title} = this.state;
        return (
            <div>
                <p>
                    <button onClick={() => this.props.fetchData()}> fetch data</button>
                </p>
                <p>
                    <input type="text" id="fname" name="fname" value={this.state.todoInfo}
                           onChange={this.handleChange}></input>
                </p>
            </div>
        );
    }
}
const mapStateToProps = (state) => {
    console.log('map state to props ' + JSON.stringify(state));
    return {
        todoInfo: state.todoReducers.todoInfo
    };
};

const mapDispatchToProps = (dispatch) => ({
    fetchData: () =>
        dispatch(fetchData()),
    changeTitle: () => dispatch(changeTitle())
});

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

Create another action:

export const changeTitle = (value) => ({
        type: CHANGE_TODO_INFO,
        payload: value
    });
};

Then handle it in your reducer:

const todoReducers = (state  = {}, action) =>{
    switch (action.type) {
        case GET_TODO:
            console.log('here ' + JSON.stringify(action));
            return {
                ...state,
                todoInfo: action.payload
            };
        case CHANGE_TODO_INFO:
            return {
                ...state,
                todoInfo: action.payload
            };
        default:
            return state;
    }
}


export default todoReducers;

Don't forget to create the CHANGE_TODO_INFO variable, you get the idea

Sign up to request clarification or add additional context in comments.

Comments

1

update title from redux in handleChange.

Comments

1

This fix should help:

handleChange = (event) => {
    const {name, value} = event.target;
    this.setState((prevState) => ({
            ...prevState,
            title: value
    }));
    
    changeTitle(value);
}

const mapDispatchToProps = (dispatch) => ({
    fetchData: () => dispatch(fetchData()),   
    changeTitle: (value) => dispatch({ type: GET_TODO, payload: value }),    
});

3 Comments

This is working. But I am getting warning Warning: A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. on initial call to handleChange. If this is recommended solution why is the warn ?
Apparently you need to set the initial value in the Redux: const todoReducers = (state = { todoInfo: '' }, action) ...
Thanks. I have tried that. still have the warn. I think its comes since i set value for input text from props which comes from redux store. Since props are not changing i think i get this warn. Solution as seems to set input text value from state. So I need to set state of component from redux props i think in this case on componenetDidUpdate method.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.