1

I have a mind boggling issue where all three of these <RecordAdmin> component instances seem to be using the state from whichever component is loaded first on page load.

I have no clue how it's happening or why, and weirdly, it was working before.

            <Switch>
                <Route path="/admin/books">
                    <RecordAdmin singular="book" plural="books" table={BookTable} form={BookForm} />
                </Route>
                <Route path="/admin/authors">
                    <RecordAdmin singular="author" plural="authors" table={AuthorTable} form={AuthorForm} />
                </Route>
                <Route path="/admin/branches">
                    <RecordAdmin singular="branch" plural="branches" table={BranchTable} form={BranchForm} />
                </Route>
            </Switch>

Using console.log, it seems as though all 3 of these components will have the same this.state.records object. Shouldn't each component instance have its own state?

Here is the source for the <RecordAdmin> component:

import React from "react";
import Axios from "axios";
import {
    Switch,
    Route,
    NavLink,
    Redirect
} from "react-router-dom";

class NewRecordForm extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            redirect: false,
        };
    }

    handleSubmit = (event, formFields, multipart = false) => {
        event.preventDefault();

        let formData = null;
        let config = null;
        if (multipart) {
            formData = new FormData();
            for (let [key, value] of Object.entries(formFields)) {
                formData.append(key, value)
            }
            config = {
                headers: {
                    'Content-Type': 'multipart/form-data'
                }
            }
        } else {
            formData = formFields;
        }

        Axios.post(`${process.env.REACT_APP_API_URL}/${this.props.plural}`, formData, config)
        .then(response => {
            this.setState({redirect: true})
        }).catch(error => {
            console.log(error)
        })
    }

    render() {
        if (this.state.redirect) {
            this.props.redirectCallback();
        }
        const Form = this.props.form
        return (
            <div>
                {this.state.redirect ? <Redirect to={`/admin/${this.props.plural}`} /> : null}
                <Form handleSubmit={this.handleSubmit} />
            </div>
        )
    }
}

function errorMessage(props) {
    return (
        <div class="alert alert-danger" role="alert">
            {props.msg}
        </div>
    )
}

export default class RecordAdmin extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            records: []
        }
    }

    componentDidMount() {
        this.loadRecords();
    }

    loadRecords = () => {
        Axios.get(process.env.REACT_APP_API_URL + '/' + this.props.plural)
        .then(response => {
            this.setState({records: response.data})
        }).catch(error => {
            console.log(error)
        })
    }

    deleteRecord = (event, recordId) => {
        event.preventDefault();
        Axios.delete(process.env.REACT_APP_API_URL + '/' + this.props.plural + '/' + recordId).then(response => {
            this.loadRecords();
        })
    }

    render() {
        // this allows us to pass props to children that are loaded via {this.props.children}
        // more on that here: https://medium.com/better-programming/passing-data-to-props-children-in-react-5399baea0356
        const TableComponent = this.props.table
        return (
            <div className="admin-body">
                {this.state.errorMessage ? <errorMessage msg={this.state.errorMessage} /> : null}
                <Switch>
                    <Route exact path={`/admin/${this.props.plural}`}>
                        <div className="admin-menu">
                            <NavLink className="btn btn-primary" to={`/admin/${this.props.plural}/new`}>New {this.props.singular.charAt(0).toUpperCase() + this.props.singular.slice(1)}</NavLink>
                        </div>
                        <TableComponent records={this.state.records} deleteRecord={this.deleteRecord} />
                    </Route>
                    <Route exact path={`/admin/${this.props.plural}/new`}>
                        <NewRecordForm plural={this.props.plural} form={this.props.form} redirectCallback={this.loadRecords}/>
                    </Route>
                </Switch>
            </div>
        );
    }
}

EDIT:

When I throw in a console.log I see that the first <RecordAdmin> that is loaded on page load, is having its records output to the console no matter which <RecordAdmin> instance is currently selected.

render() {
    // this allows us to pass props to children that are loaded via {this.props.children}
    // more on that here: https://medium.com/better-programming/passing-data-to-props-children-in-react-5399baea0356
    const TableComponent = this.props.table
    console.log(this.records) // No matter which <RecordAdmin> is currently being displayed, the records will be the records from whichever <RecordComponent was first loaded on page load.
    return (
        <div className="admin-body">
            {this.state.errorMessage ? <errorMessage msg={this.state.errorMessage} /> : null}
            <Switch>
                <Route exact path={`/admin/${this.props.plural}`}>
                    <div className="admin-menu">
                        <NavLink className="btn btn-primary" to={`/admin/${this.props.plural}/new`}>New {this.props.singular.charAt(0).toUpperCase() + this.props.singular.slice(1)}</NavLink>
                    </div>
                    {console.log(this.state.records)}
                    <TableComponent records={this.state.records} deleteRecord={this.deleteRecord} />
                </Route>
                <Route exact path={`/admin/${this.props.plural}/new`}>
                    <NewRecordForm plural={this.props.plural} form={this.props.form} redirectCallback={this.loadRecords}/>
                </Route>
            </Switch>
        </div>
    );
}

No matter which <RecordAdmin> instance is being displayed, using console.log shows that state is being shared between all 3 <RecordAdmin> instances.

Blaine Lafreniere
  • 3,451
  • 6
  • 33
  • 55
  • Does this answer your question? [using same component for different route path in react-router v4](https://stackoverflow.com/questions/49001001/using-same-component-for-different-route-path-in-react-router-v4) – Brian Thompson May 20 '20 at 15:22
  • You can verify that the same component instance is being used in all three routes by console logging in the constructor. Adding a unique `key` to each should fix the problem and inform react that it should indeed be a new component instance and not just a change of props. – Brian Thompson May 20 '20 at 15:23

1 Answers1

0

You can use different key for each instance of RecordAdmin and maybe pass exact={true} just to be sure.

Muhammad Ali
  • 2,538
  • 2
  • 16
  • 21