"Child already has a parent, it must be removed first" when i'm reordering a table in react

Viewed 1527

I'm trying to ordenate a material-react-table in react.

In the column that i need to ordenate, i put this function:

<TableCell onClick={() => sortBy('login')}>Email</TableCell>

const sortBy = (key) => {
    // If is using a field different by the last, starts ordering in asc
    if (key !== orderingField) {
        setOrderingStatus('asc')
        setOrderingField(key)
    // Reverte a ordenação
    } else { 
        if (orderingStatus === 'asc') {
            setOrderingStatus('desc')
        } else {
            setOrderingStatus('asc')
        }
    }
    let copyUsers = {}
    copyUsers.data = [...users] // make a copy of the obj
    copyUsers.lastPage = JSON.parse(JSON.stringify(lastPage)) // copy of the last page 
    copyUsers.data.sort(compareValues(orderingField, orderingStatus)) // make the sort)
    dispatch(userActions.setUserList(copyUsers))
}

This is my function that ordering:

export default function compareValues(key, order = 'asc') {
    return function innerSort(a, b) {
      if (!a.hasOwnProperty(key) || !b.hasOwnProperty(key)) {
        // property doesn't exist on either object
        return 0;
      }

      const varA = (typeof a[key] === 'string')
        ? a[key].toUpperCase() : a[key];
      const varB = (typeof b[key] === 'string')
        ? b[key].toUpperCase() : b[key];

      let comparison = 0;
      if (varA > varB) {
        comparison = 1;
      } else if (varA < varB) {
        comparison = -1;
      }
      return (
        (order === 'desc') ? (comparison * -1) : comparison
      );
    };
}

When i see in my reducer the request payload is comming like the expected, so, the ordering is been done, but this don't render again my table and i get some errors:

Child already has a parent, it must be removed first.

nbind.js:9812 Uncaught abort()

I'm using this redux useSelector() to get the value from of my table from the store:

const users = useSelector(state => state.userStates.users)

So in my table i show iterating by:

{users && users.map(user => (

Someone can show me why this is happening?

After a very hard debug, i discovered that the problem is something in the copy of the obj. When i do a copy with reference, like:

copyUsers.data = users

Everything works, but when i try a copy without reference, like:

copyUsers.data = JSON.parse(JSON.stringify(users))

or

copyUsers.data = [...users]

I receive the error.

Someone can explain this?

My package.json dependencies:

"dependencies": {
    "@material-ui/core": "4.9.5",
    "@material-ui/icons": "^4.9.1",
    "@material-ui/lab": "^4.0.0-alpha.45",
    "@react-pdf/renderer": "^1.6.8",
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.3.2",
    "@testing-library/user-event": "^7.1.2",
    "axios": "^0.19.2",
    "formik": "^2.1.4",
    "immer": "^6.0.1",
    "notistack": "^0.9.8",
    "react": "^16.13.0",
    "react-dom": "^16.13.0",
    "react-export-excel": "^0.5.3",
    "react-history": "^0.18.2",
    "react-html2pdf": "^1.0.1",
    "react-input-mask": "^2.0.4",
    "react-redux": "^7.2.0",
    "react-router-dom": "^5.1.2",
    "react-scripts": "3.4.0",
    "redux": "^4.0.5",
    "redux-saga": "^1.1.3",
    "styled-components": "^5.0.1",
    "yup": "^0.28.1"
  },

My pdf component:

<Grid item>
    <UsersPDF />
</Grid>

This is my UsersPDF:

class UsersPDF extends Component {

    constructor(props) {
        super(props);
        this.state = {
            ready: false
        };
    }

    componentDidMount() {
        this.setState(
            () => ({
                ready: false
            }),
            () => {
                setTimeout(() => {
                    this.setState({ ready: true });
                }, 1);
            }
        );
    }

    render() {
        const { users } = this.props;
        const { ready } = this.state;

        const doc = (
            <Document>
                <Page style={styles.body}>
                    <View style={styles.rowCabecalho}>
                        <Text style={styles.nomeRelatorio}>Relatório de usuários</Text>
                    </View>
                    <View style={styles.table}>
                        <View style={styles.tableRow}>
                            <View style={styles.tableCol}>
                                <Text style={styles.tableCell}>Email</Text>
                            </View>
                            <View style={styles.tableCol}>
                                <Text style={styles.tableCell}>
                                    Filial logada
                                </Text>
                            </View>
                            <View style={styles.tableCol}>
                                <Text style={styles.tableCell}>Ativo</Text>
                            </View>
                        </View>
                        {users && users.map(user => (
                        <View key={user.id} style={styles.tableRow}>
                            <View style={styles.tableCol}>
                                <Text style={styles.tableCell}>{user.login}</Text>
                            </View>
                            <View style={styles.tableCol}>
                                <Text style={styles.tableCell}>{user.company_name}</Text>
                            </View>
                            <View style={styles.tableCol}>
                                <Text style={styles.tableCell}>
                                    {user.inactive === true ? 'Não' : 'Sim'}
                                </Text>
                            </View>
                        </View>
                        ))}
                    </View>
                </Page>
            </Document>
        );

        return (
            <>
                {ready && (
                    <PDFDownloadLink style={{textDecoration: 'none'}} className={styles.PDFDownloadLink} document={doc} fileName="usuarios.pdf">
                        {({ blob, url, loading, error }) =>
                            loading ? <></> : <Button variant="contained" color="primary">PDF</Button>
                        }
                    </PDFDownloadLink>
                )}
            </>
        );
    }
}

const mapStateToProps = state => ({
    users: state.userStates.users
});

export default connect(mapStateToProps)(UsersPDF);
1 Answers

EDIT 2: This is a known error there are some workarounds on this issue.

I am fairly sure that the error is in another component, this github issue (on @react-pdf/renderer) seems to be the same one you are having. They say it works the first time but throws the Child already has a parent, ... error after that.

What (I think) is happening is when state.userStates.users gets updated, the component that uses @react-pdf/renderer gets updated and leaks memory causing the error.

This doesn't happen when you don't clone the array because redux/react doesn't register it as a state change so the component doesn't re-render.

Solution from @willywill on github

const renderToStream = async function (element) {
  const instance = pdf(element);
  const buffer = await instance.toBuffer(); 
  instance.container.finish(); // This cleans up objects from memory (ADD ME)
  return buffer;
};

Edit: If you are using renderToStream make sure to use @react-pdf/renderer >=1.5.6 as it fixes this bug. (bug fix, 1.5.6 release)

Related