Search code examples
javascriptreactjsreact-routerreact-statereact-state-management

React component loads data twice on moving back-and-forth between tabs


For some reason my React component seems to remember its old state when going to another tab and back again, instead of reloading completely.

Basically when I click on the "Create" tab in my navbar and back to the "Board" tab data is populated twice instead of once, see image below. When going back the Board component this.state has two of each taskIds, as if it the component state still had the data from the initial page load when loading again. I have a React component looking like this:

enter image description here

const columnOrder = ['todo', 'in-progress', 'in-review', 'done']
const EMPTY_COLUMNS = {
  'todo': {
    id: 'todo',
    title: 'TODO',
    taskIds: []
  },
  'in-progress': {
    id: 'in-progress',
    title: 'In Progress',
    taskIds: [],
  },
  'in-review': {
    id: 'in-review',
    title: 'In Review',
    taskIds: []
  },
  'done': {
    id: 'done',
    title: 'Done',
    taskIds: []
  }
};

export class Board extends Component {
   constructor(props) {
        super(props);
        this.onLoadEpic = this.onLoadEpic.bind(this);
        this.state = {
            columnOrder: columnOrder,
            columns: {
                'in-progress': {
                    id: 'in-progress',
                    title: 'In Progress',
                    taskIds: [],
                },
            // ...more columns similar to above
            },
        };
    
    // Load state data on mount
    componentDidMount() {
        loadEpic(arg1, arg2);
    }

    // Async function loading items from DB and formatting into useful columns
    async loadEpic(arg1, arg2) {
        axios.get(...)
            .then((response) => {
                let data = response.data;
                let newTasks = {};
                let newColumns = EMPTY_COLUMNS;
                
                data.taskItems.forEach(function(item) {
                   let id = item.id.toString();
                   newColumns[item.status]["taskIds"].push(id);
                   newTasks[id] = {
                       ...item,
                       id: id
                   }
                });
                this.setState({
                    tasks: newTasks,
                    columns: newColumns
                });
            })
    }

    render() {
        // Prints ["7"] on initial load and ["7", "7"] after going back and forth
        console.log(this.state.columns["in-progress"].taskIds);
        return (
          // Simplified, but this is the main idea
          <Container>
          <DragDropContext  onDragEnd={this.onDragEnd}>
              {
                  this.state.columnOrder.map((columnId) => {
                      const column = this.state.columns[columnId]
                      const tasks = column.taskIds.map(taskId => this.state.tasks[taskId]
                      return (
                          <Column key={column.id} column={column} tasks={tasks}/>
                      )
                  }
              }
          </DragDropContext>
          </Container>
        )
    }

}

and an App.js with Routing looking like this:

export default class App extends Component {
  static displayName = App.name;

  render () {
    return (
      <Layout>
        <Route exact path='/' component={Board} />
        <Route exact path='/create' component={Create} />
      </Layout>
    );
  }
}

Solution

  • Okay, so I figured it out: it's the EMPTY_COLUMNS constant that is bugging out. When the component is re-rendered, the same EMPTY_COLUMNS object is referenced - so the constant is being appended to. Instead, I should make a copy of the empty columns:

    // Before - same object is being appended to, doesn't work
    let newColumns = EMPTY_COLUMNS;
    // After - create a deep copy of the constant, does work
    let newColumns = JSON.parse(JSON.stringify(EMPTY_COLUMNS));