react-tabulator icon indicating copy to clipboard operation
react-tabulator copied to clipboard

How can I create a custom link formatter?

Open 8483 opened this issue 5 years ago • 11 comments

I cannot for the life of me get the formatter to work.

I get blank cells instead of a formatted link.

The React documentation could really use some explanations.

Side-question: Can I use the vanilla Tabulator in React, instead of the adapted one?

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

function Journals(props) {

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

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

    const journalLinkFormatter = (cell, formatterParams) => {
        let key = cell.getValue()
        let link = `/journals/${key}`
        return <Link style={{ color: "blue", fontWeight: "bold", background: "red" }} to={link}>{key}</Link>
    }

    const columns = [
        { title: "Number", field: "key_", formatter: journalLinkFormatter },
        { title: "Date", field: "date_" },
    ];

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

export default Journals;

This way works, but it beats the purpose, as it reloads everything.

const columns = [
        {
            title: "Number", 
            field: "key_", 
            formatter: "link", 
            formatterParams: { url: cell => { return "/journals/" + cell.getValue() } }
        },
        { title: "Date", field: "date_" },
    ];

I'd like to learn how to use custom formatters.

8483 avatar Oct 27 '19 21:10 8483

@8483 have you tried this example? https://github.com/ngduc/react-tabulator/blob/master/src/ReactTabulatorExample.tsx#L22

ngduc avatar Oct 27 '19 22:10 ngduc

How do I make your example work in my case. I really have no idea.

I am getting a Link outside a Router error, for no reason. Any advice?

https://stackoverflow.com/questions/58645310/link-outside-a-router-error-while-everything-set-up-properly

Here's a CodeSandbox that demonstrates the problem.

8483 avatar Oct 30 '19 00:10 8483

I got this reply on StackOverflow.

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.

8483 avatar Oct 31 '19 23:10 8483

@8483 I will look into this to see any alternative to ReactDOM.render If anyone can help, it'll be great.

ngduc avatar Nov 14 '19 16:11 ngduc

Thank you for considering it! It really is a must have.

8483 avatar Nov 14 '19 16:11 8483

Can we use portals for this instead?

ericnkatz avatar Dec 03 '19 21:12 ericnkatz

As of right now anytime you interact with the Table in a way that triggers reload (sorting by column for instance) it continually instantiates and appends a completely unique React instance to the DOM. Sort two or three times and all of a sudden your 10 rows with 2-3 custom ReactFormatted cells are now hundreds of mounted and abandoned React DOM/rendered instances.

ericnkatz avatar Dec 03 '19 21:12 ericnkatz

@8483 @ericnkatz I fixed the reactFormatter so it can re-render on cell edit, new version 0.12.0.

will look into react dom & react-router.

ngduc avatar Apr 09 '20 07:04 ngduc

Cf. this react doc, shouldn't there be an "opposite" action of ReactDOM.render()? I.e. call ReactDOM.unmountComponentAtNode() when the cell is disposed, so that some cleanup is done?

It's normal that Link doesn't work. The Router instance from the main react tree needs to be somehow stored as a global plain JS var and passed to tabulator. And then, within the custom renderer, provided via Context. This way, Link retrieves the Router instance.

cristian-spiescu avatar Apr 27 '20 20:04 cristian-spiescu

I have created an implementation using portals in this gist. It's a Storybook pages. I'm using semantic-ui-react in my project, so a copy/paste of the file may need adapting.

I don't know if it's OK to have thousands of portals. I.e. one per cell. Also, I didn't see any action do "dispose" a portal. I hope that disposing the DOM element where the portal was rendered suffices. Otherwise => memory leaks possible I think.

cristian-spiescu avatar Jun 11 '20 09:06 cristian-spiescu

Easy way is conditional rendering all page with tabulator So, this way warked for me:

const [redirect, setRedirect] = useState({ allow: false })
const columns = [
        { title: 'No', field: 'no', },
        {
            title: 'Patient', field: 'patient', cellClick: function (e, cell) {
                const rowInfo = cell._cell.row.data
                setRedirect({ allow: true, id: rowInfo.id }) //here we update state
            }
        },
]
//....
if (redirect.allow) {
        return <Redirect to={{
            pathname: '/accounts/profile',
            state: {
                id: redirect?.id
            }
        }} />
    } else {
   //...
}

Maxssobolev avatar Aug 31 '21 13:08 Maxssobolev