0

I try to convert the class component in my react app below :

import React, { Component } from 'react'
import ReactTable from 'react-table'
import api from '../api'

import styled from 'styled-components'

import 'react-table/react-table.css'

const Wrapper = styled.div`
    padding: 0 40px 40px 40px;
`

const Update = styled.div`  
    color: #ef9b0f;
    cursor: pointer;
`

const Delete = styled.div`
    color: #ff0000;
    cursor: pointer;
`

class UpdateVoter extends Component {
    updateUser = event => {
        event.preventDefault()

        window.location.href = `/voters/update/${this.props.id}`
    }

    render() {
        return <Update onClick={this.updateUser}>Update</Update>
    }
}

class DeleteVoter extends Component {
    deleteUser = event => {
        event.preventDefault()

        if (
            window.confirm(
                `Do you want to delete this voter ${this.props.id} permanently?`,
            )
        ) {
            api.deleteVoterById(this.props.id)
            window.location.reload()
        }
    }

    render() {
        return <Delete onClick={this.deleteUser}>Delete</Delete>
    }
}

class VotersList extends Component {
    constructor(props) {
        super(props)
        this.state = {
            voters: [],
            columns: [],
            isLoading: false,
        }
    }

    componentDidMount = async () => {
        this.setState({ isLoading: true })

        await api.getAllVoters().then(voters => {
            this.setState({
                voters: voters.data.data,
                isLoading: false,
            })
        })
    }

    render() {
        const { voters, isLoading } = this.state

        const columns = [
            {
                Header: 'ID',
                accessor: '_id',
                filterable: true,
            },
            {
                Header: 'No KK',
                accessor: 'nkk',
                filterable: true,
            },
            {
                Header: 'NIK',
                accessor: 'nik',
                filterable: true,
            },
            {
                Header: 'Nama',
                accessor: 'nama',
                filterable: true,
            },
            {
                Header: 'Alamat',
                accessor: 'alamat',
                filterable: true,
            },
            {
                Header: '',
                accessor: '',
                Cell: function(props) {
                    return (
                        <span>
                            <DeleteVoter id={props.original._id} />
                        </span>
                    )
                },
            },
            {
                Header: '',
                accessor: '',
                Cell: function(props) {
                    return (
                        <span>
                            <UpdateVoter id={props.original._id} />
                        </span>
                    )
                },
            },
        ]

        let showTable = true
        if (!voters.length) {
            showTable = false
        }

        return (
            <Wrapper>
                {showTable && (
                    <ReactTable
                        data={voters}
                        columns={columns}
                        loading={isLoading}
                        defaultPageSize={10}
                        showPageSizeOptions={true}
                        minRows={0}
                    />
                )}
            </Wrapper>
        )
    }
}

export default VotersList

to the functional component with hooks, like this :

import React, {useState, useEffect} from 'react'
import ReactTable from 'react-table'
import api from '../api'

import styled from 'styled-components'

import 'react-table/react-table.css'

const Wrapper = styled.div`
    padding: 0 40px 40px 40px;
`

const Update = styled.div`
    color: #ef9b0f;
    cursor: pointer;
`

const Delete = styled.div`
    color: #ff0000;
    cursor: pointer;
`

function UpdateVoter(props) {
    const updateUser = event => {
        event.preventDefault()

        window.location.href = `/voters/update/${props.id}`
    }

    
        return <Update onClick={updateUser}>Update</Update>

}

function DeleteVoter(props) {
    const deleteUser = event => {
        event.preventDefault()

        if (
            window.confirm(
                `Do tou want to delete this voter ${props.id} permanently?`,
            )
        ) {
            api.deleteVoterById(props.id)
            window.location.reload()
        }
    }

    
        return <Delete onClick={deleteUser}>Delete</Delete>
  
}

function VotersListSpecific(props) {
    const [state, setState] = useState ({
            voters: [],
            columns: [],
            isLoading: false,
        })
   

    useEffect(() => {
        async function fetchData() {
            setState({ ...state, isLoading: true })

            let voters = await api.getAllVoters()
            setState({
                voters: voters.data.data, 
                ...state,
                isLoading: true,
            })
        }
        fetchData()
        console.log(fetc)
    }, [])

        const { voters, isLoading } = state

        const columns = [
            {
                Header: 'ID',
                accessor: '_id',
            },
            {
                Header: 'No KK',
                accessor: 'nkk',
            },
            {
                Header: 'NIK',
                accessor: 'nik',
            },
            {
                Header: 'Nama',
                accessor: 'nama',
            },
            {
                Header: 'Alamat',
                accessor: 'alamat',
            },
            {
                Header: '',
                accessor: '',
                Cell: function(props) {
                    return (
                        <span>
                            <DeleteVoter id={props.original._id} />
                        </span>
                    )
                },
            },
            {
                Header: '',
                accessor: '',
                Cell: function(props) {
                    return (
                        <span>
                            <UpdateVoter id={props.original._id} />
                        </span>
                    )
                },
            },
        ]

        let showTable = true
        if (!voters.length) {
            showTable = false
        }

        return (
            <Wrapper>
                {showTable && (
                    <ReactTable
                        data={voters}
                        columns={columns}
                        loading={isLoading}
                        defaultPageSize={10}
                        showPageSizeOptions={true}
                        minRows={0}
                    />
                )}
            </Wrapper>
        )
    
}

export default VotersList

But, the code is not working. The table is not displayed. The "voters" array inside the state is empty. Besides that, I also got this warning :

React Hook useEffect has a missing dependency: 'state'. Either include it or remove the dependency array. You can also do a functional update 'setState(s => ...)' if you only need 'state' in the 'setState' call react-hooks/exhaustive-deps

I really need help to solve this. Thank you in advance.

2 Answers 2

1

My Suggestion

First of all, useState is not like the this.state in class components. You are recommended to assign each primitive state with a useState function. For example, in you VotersListSpecific component, instead of having one state wrapping up voters, columns and isLoading, you could have:

const [voters, setVoters] = useState([])
const [columns, setColumns] = useState([])
const [isLoading, setLoading] = useState(false)

Still it can be further optimized using useReducer, but it would be way too off-topic for now. You can checkout the official docs if interested.

Your Problem

Now let us analyze what you did right and what you did wrong.

The Rights

When you change the state, you use the syntax setState({ ...state, isLoading: true }), which is correct as React only toggles the re-render when the reference of the object is changed. If you use something like this:

state.isLoading = false
setState(state)

The reference if the state is not changed in this case, so React will not re-render.

The Wrongs

When you call const { voters, isLoading } = state, the voters variable points to the voters field of the state, which is an empty array at the time of first render. Some time later, when the new state is created with the new voters, the new state.voters actually points to a new array. But React does not know about this change as you have explicitly pointed the voter variable to the original empty array, which is a field of the old state.

A fix to this would be something like:

const [voters, setVoters] = useState([])
useEffect(async () => {
  setVoters(await api.getAllVoters())
}, [])
return (
  <ReactTable data={voters}/>
)

Another approach can be using data={state.voters}.

Sign up to request clarification or add additional context in comments.

6 Comments

I'm sorry. What do you mean by "the new state.voters actually points to a new array"? What I understand is after the first render, state is changed by useEffect function, and trigger the second render, which fill the voters variable with the new state (state that contains data from api.getAllVoters function). Please correct me if I'm wrong
In you original post, you used data={voters} to pass the voters, an array, to the component props. But when you called setState, you are changing the state object, not the voters object. You can either use data={state.voters} to tell React to watch the change of state object, or better, assign voters using an individual useState, just like my example.
it seems the concept of reference vs value is confusing you here. There are dozens of materials online that explains this concept, a quick search gave me this post which seems helpful.
I have used data={state.voters}, but I still got the same result (the table is not displayed)
Hmm, I will check this approach again locally once I've got spare time. Have you tried another fix const [voters, setVoters] = useState([])?
|
0

Welcome to React Functional programming.

I don't have all your dependencies so I'm trying to answer your question by eye instead of actual compiling.

A grain of salt when updating state is, instead of doing this:

setState({
   ...state,
   ...yourstuffhere
})

do this:

setState(prevState => {
    return {
        ...prevState,
        ...yourstuffhere
    }
});

What happens is by doing above, you are making sure that you have updated data from previous state. Remember that setState is an async function. Unless you do it like that, you might accidentally lose some data on the fly.

useEffect(() => {
    async function fetchData() {
        setState(prevState => { 
            return {
                ...prevState, isLoading: true 
            }
        })

        let voters = await api.getAllVoters()
        setState(prevState => {
            return {
                voters: voters.data.data, 
                ...prevState,
                isLoading: true,
            }
        })
    }
    fetchData()
    console.log(fetc)
}, [])

1 Comment

I've tried that. But I got this error : (anonymous function) 70 | let voters = api.getAllVoters() 71 | setState((prevState) => { 72 | return { > 73 | voters: voters.data.data, | ^ 74 | ...prevState, 75 | isLoading: true 76 | }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.