2

I am trying to create a form where the user can edit the information of a contact from a database and then save those changes. The form correctly loads and saves the information from the database, but when componentDidMount() loads the current information into the form fields, the fields don't re-render, causing both the template text (ie. Name, Email, etc) and the information loaded from the database to display on top of each other. Clicking any of the input fields causes the field that was clicked to render properly with the text displaying above the input field, rather than over the loaded text.

How can I force the form to rerender each field after the data has been loaded properly in componentDidMount()? I've tried forceUpdate() but it didn't work, and I haven't been able to find anyone else with this issue. I suspect it has something to do with me using the bootstrap styles wrong, but even after reading all of bootstrap's documentation I couldn't find anything related to my issue.

Here are a couple of screenshots to show the issue.

The form right after loading

The form right after loading

The form after clicking the 'Name' field

The form after clicking the 'Name' field

Here's the relevant code for the component.

    constructor(props) {
        super(props);

        this.state = {
            name: "",
            email: "",
            role: "",
            phone: "",
            errors: {}
        }
    }

    // Initializer: Copy contact's current data to state
    componentDidMount() {
        axios.get("/api/contacts/get/" + this.props.match.params.id)
            .then(res => {
                this.setState({ name: res.data.name });
                this.setState({ email: res.data.email });
                this.setState({ role: res.data.role });
                this.setState({ phone: res.data.phone });
            })
            .catch(err => console.log(err));

        this.forceUpdate();
    }

    // Change handler: Modify values when changed
    onChange = e => {
        this.setState({ [e.target.id]: e.target.value });
    }

    // Submit handler: Save changes to database
    onSubmit = e => {
        e.preventDefault();

        const contact = {
            name: this.state.name,
            email: this.state.email,
            role: this.state.role,
            phone: stripNumber(this.state.phone)
        };

        // Post modified contact to database, then navigate back to Manage Contacts Dashboard
        axios.post("/api/admin/contacts/update/" + this.props.match.params.id, contact);
        window.location = PREVIOUS_URL;
    }

    render() {
        const { errors } = this.state;

        return (
            <div>
                <Link to={PREVIOUS_URL} className="btn-flat waves-effect">
                    <i className="material-icons left">keyboard_backspace</i> Back to Manage Contacts
                </Link>
                <h3>Edit contact</h3>
                <form noValidate onSubmit={this.onSubmit}>
                    <div className="input-field col s12">
                        <input
                            onChange={this.onChange}
                            value={this.state.name}
                            error={errors.name}
                            id="name"
                            type="text"
                            className={classnames("", { invalid: errors.name })}
                        />
                        <label htmlFor="name">Name</label>
                        <span className="red-text">{errors.name}</span>
                    </div>
                    <div className="input-field col s12">
                        <input
                            onChange={this.onChange}
                            value={this.state.email}
                            error={errors.email}
                            id="email"
                            type="email"
                            className={classnames("", { invalid: errors.email || errors.emailnotfound })}
                        />
                        <label htmlFor="email">Email</label>
                        <span className="red-text">{errors.email}{errors.emailnotfound}</span>
                    </div>
                    <div className="input-field col s12">
                        <input
                            onChange={this.onChange}
                            value={this.state.role}
                            error={errors.role}
                            id="role"
                            type="text"
                            className={classnames("", { invalid: errors.role })}
                        />
                        <label htmlFor="role">Role</label>
                        <span className="red-text">{errors.role}</span>
                    </div>
                    <div className="input-field col s12">
                        <input
                            onChange={this.onChange}
                            value={this.state.phone}
                            error={errors.phone}
                            id="phone"
                            type="tel"
                            className={classnames("", { invalid: errors.phone })}
                        />
                        <label htmlFor="phone">Phone Number</label>
                        <span className="red-text">{errors.phone}</span>
                    </div>
                    <div className="form-group">
                        <input type="submit" value="Submit changes" className="btn btn-primary" />
                        <Link to={PREVIOUS_URL} className="btn btn-danger">  Cancel  </Link>
                    </div>
                </form>
            </div>
        )
    }
}
3
  • It's not really clear what you think is going on, but when state is updated the component will be rerendered. It looks like you've some placeholder text in the first screen cap. Other than the unnecessary forceUpdate I don't see any overt issues in your snippet. Can you create a running codesandbox that reproduces this issue that we can live debug in? Commented Apr 4, 2021 at 2:32
  • Could you show where the template text is coming from, because there doesn't seem to be any code shown that produces this template text. If you are using a css framework, that may be part of the problem. Commented Apr 4, 2021 at 4:44
  • The template text is being loaded from the database in componentDidMount(). According to react's docs setState() and forceUpdate() should both cause the component to rerender, but the input fields don't display properly until the user clicks them. The form should place the label (for example 'Name') into the input field until a user enters text into the field, causing the label to move up to be above the field. This works normally when filling in the fields manually, but when the text is loaded from the database the input fields don't seem to update and the text displays over the label. Commented Apr 4, 2021 at 15:45

1 Answer 1

1

Have you tried doing the data fetch inside componentWillMount()?

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

1 Comment

Yes, but it doesn't change anything. Also isn't componentWillMount considered legacy and will not continue to receive support?

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.