Sir Rubberduck
Sir Rubberduck

Reputation: 2282

Link outside a Router Error, while everything set up properly

Ok, I have no idea why this is not working. Everything is set up properly from what I can see.

I am using "react-router-dom": "^5.0.0"

The code also uses the Tabulator grid library, specifically the React implementation of it. It's not really relevant, just wanted to note it.

The code works 100% without using the sub-component links, so the problem is not there.

The grid generator in Journals creates a table, which has link cells, which lead to the Journal component.

The link component is generated fine, it just doesn't work for reasons I don't know.

CodeSandbox

If you comment out the formatter line in columns in the Journal component, the app works again.

App.js

import React, { Component } from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import Header from './components/layout/Header';
import Dashboard from './components/pages/Dashboard';
import Journals from './components/pages/Journals';
import Journal from './components/pages/Journal';

class App extends Component {
  render() {
    return (
      <Router>
        <div className="App">
          <div className="container">
            <Header />
            <div className="content">
              <Route exact path="/" component={Dashboard} />
              <Route exact path="/journals" component={Journals} />
              <Route path="/journals/:key" component={Journal} /> //    <------ ROUTE IS HERE
            </div>
          </div>
        </div>
      </Router>
    );
  }
}

export default App;

Journals.js

import React, { useState, useEffect } from "react";
import { Link } from 'react-router-dom';
import { ReactTabulator } from 'react-tabulator'
import "tabulator-tables/dist/css/tabulator.min.css";
import { reactFormatter } from 'react-tabulator';

function Journals() {

    const [journals, setJournals] = useState([]);

    useEffect(() => {
        fetch("http://localhost:4000/journals")
            .then(res => res.json())
            .then(data => {
                setJournals(data)
            })
            .catch(err => err);
    }, []);

    const JournalLink = (props) => {
        const cellData = props.cell._cell.row.data;
        let key = cellData.key_
        let link = `/journals/${key}`
        return <Link to={link}>{key}</Link>; //    <------ LINK COMPONENT IS HERE
    }

    const columns = [
        {
            title: "Number", 
            field: "key_", 
            formatter: reactFormatter(<JournalLink />) //    <------ LINK COMPONENT USED HERE
        },
        { title: "Date", field: "date_" },
    ];

    return (
        <div>
            <h1>Journals</h1>
            <ReactTabulator
                data={journals}
                columns={columns}
                tooltips={true}
                layout={"fitData"}
            />
        </div >
    )
}

export default Journals;

reactFormatter usage example
reactFormatter definition

Journal.js

import React, { useState, useEffect } from "react";
import { ReactTabulator } from 'react-tabulator'
import "tabulator-tables/dist/css/tabulator.min.css";

function Journal(props) {

    const [journalItems, setJournalItems] = useState([]);

    const initialFormJournalItems = {
        id: "",
        journalId: "",
        companyId: "",
        documentKey: "",
        documentDate: "",
        debitAccount: "",
        debit: "",
        creditAccount: "",
        credit: ""
    }

    const [formJournalItems, setFormJournalItems] = useState(initialFormJournalItems);

    useEffect(() => {
        fetch(`http://localhost:4000/journals/${props.match.params.key}`)
            .then(res => res.json())
            .then(data => {
                setJournalItems(data)
            })
            .catch(err => err);
    }, []); 

    const columns = [
        { title: "Document", field: "documentKey" },
        { title: "Date", field: "documentDate" },
    ];

    return (
        <div>
            <h1>Journal</h1>
            <ReactTabulator
                data={journalItems}
                columns={columns}
                tooltips={true}
                layout={"fitData"}
            />
        </div >
    )
}

export default Journal;

Upvotes: 0

Views: 273

Answers (1)

Oluwafemi Sule
Oluwafemi Sule

Reputation: 38962

react-tabulator reFormatter is incompatible with react-router library.

https://github.com/ngduc/react-tabulator/blob/0.10.3/lib/Utils.js#L30

From source code,

function reactFormatter(JSX) {
    return function customFormatter(cell, formatterParams, onRendered) {
        //cell - the cell component
        //formatterParams - parameters set for the column
        //onRendered - function to call when the formatter has been rendered
        onRendered(function () {
            var cellEl = cell.getElement();
            var CompWithMoreProps = React.cloneElement(JSX, { cell: cell });
            react_dom_1.render(CompWithMoreProps, cellEl.querySelector('.formatterCell'));
        });
        return '<div class="formatterCell"></div>';
    };
}

rendering of a formatted element uses the ReactDOM.render function to render the formatted element directly to DOM isolated from parent elements.

A fix to react-tabulator needs to be done to support this use case. One way to go is to have customFormatter return a custom component that provides a way to set its state from outside it. Then onRendered can call this function to set cell.

Upvotes: 1

Related Questions