Converting Class Component to Functional Component With Hooks - javascript

I tried to convert the class component code 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 (!this.state.voters.length) {
showTable = false
}
return (
<Wrapper>
{showTable && (
<ReactTable
data={this.state.voters}
columns={columns}
loading={this.state.isLoading}
defaultPageSize={10}
showPageSizeOptions={true}
minRows={0}
/>
)}
</Wrapper>
)
}
}
export default VotersList
to this functional component code :
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 VotersList(props) {
const [voters, setVoters] = useState ({voters: []})
const [isLoading, setIsLoading] = useState ({isLoading: false})
useEffect(() => {
async function fetchData() {
setIsLoading(true)
return (setVoters(await api.getAllVoters()))
}
console.log(fetchData())
}, [])
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, I got the blank result. The table is not displayed. I tried to console.log(fetchData()) inside useEffect function, and I got this result PromiseĀ {<pending>} printed in the console. What does it mean? And why is the table not displayed as it should be? Thank you very much in advance.

What does it mean?
Promise {<pending>} is telling you that the result of the function is a promise that has not yet resolved. An async function will return a Promise when invoked without await;
If you want to view the contents of your network request you should console.log within your fetchData function.
And why is the table not displayed as it should be?
I think this is occurring because you are not setting the getAllVoters result correctly.
In your original code you set the state variable voters to data.data from the API result, whereas in the refactored code you simple set it to the result with:
setVoters(await api.getAllVoters())
You could fix this by changing it to:
useEffect(() => {
async function fetchData() {
setIsLoading(true)
const voters = await api.getAllVoters();
setVoters(voters.data.data)
}
fetchData()
}, [])
It is also worth mentioning that you are using useState incorrectly.
You are using it like this:
const [isLoading, setIsLoading] = useState({ isLoading: false });
Whereas it should be used like this:
const [isLoading, setIsLoading] = useState(false);
At the moment you are setting the variable isLoading to this object: {isLoading: false} whereas you simply want to set it to false.
This isn't causing you problems at the moment as you are immediately changing the value to true with setIsLoading(true), however, it will probably cause bugs down the line.
The same is true for useState ({voters: []}).

You are returning setVoter from fetchVoter function inside useEffect, that's why console is printing a promise.
You should try like this
async function fetchData() {
const data = await api.getAllVoters()
return data
}
async handleDataFetch() {
setIsLoading(true)
const data = await fetchData()
setVoters(data)
setIsLoading(false)
}
useEffect(() => {
handleDataFetch()
}, [])

I believe you're not using useEffect hook effectively. There are few problems I've seen in your code one of them is using async with a function(which is correct) but the rule of async-await is that when you have an async function you got to await wherever you call it.
Also the good practice is to put your api logic outside useEffectin a separate function.
There's one more issue I found in your code is your isLoading state is initialized as an object but then in your fetctData function you're set-ting it as a bool value which is wrong. You can simply initialized it to be true and set it to false after data has been fetched
So your above component code of VotersList will look something like this
function VotersList(props) {
const [voters, setVoters] = useState({voters: []})
const [isLoading, setIsLoading] = useState(true)
const fetchData = async () => {
let allVoters = await api.getAllVoters();
setVoters(allVoters);
setIsLoading(false);
}
useEffect(async () => {
let allVoters = await api.getAllVoters();
setVoters(allVoters);
}, [])
// OR
/*
useEffect(async () => {
await fetchData();
}, [])
*/
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>
)
}

Related

How do I call a function which is inside a function component in React js 18?

I'm using a npm package called ReactDataGrid which has SelectEditor module which renders a combo box. In the editorProps, I am able to set a function which needs to be called on onChange event. This function setClientonChange needs to call another function which is nested inside a function component? How can I call it?
import React, {useState } from 'react';
import ReactDataGrid from '#inovua/reactdatagrid-community';
const columns = [
...
{ name: 'currency_id', groupBy: false, defaultFlex: 1, maxWidth: 150, textAlign: 'center', header: 'Currency', editor: SelectEditor, editable:true,
editorProps: {
dataSource: ['Dollar', 'Euro', 'Pound', 'INR'].map((element) => ({
id: element,
label: element
})),
setClientonChange(){
//have to call setCurrencyValue() here
}
}
}
];
const RoomDeposit = () => {
const [gridRef, setGridRef] = useState(null);
const setCurrencyValue = () => {
gridRef.current.setItemPropertyAt(2, 'amount', '20')
}
return (
<ReactDataGrid
onReady={setGridRef}
columns={columns}
dataSource={dataSource}
/>
);
}
export default () => <RoomDeposit />
import React, { useState } from "react";
import ReactDataGrid from "#inovua/reactdatagrid-community";
const columns = (setCurrencyValue) => [
...{
name: "currency_id",
groupBy: false,
defaultFlex: 1,
maxWidth: 150,
textAlign: "center",
header: "Currency",
editor: SelectEditor,
editable: true,
editorProps: {
dataSource: ["Dollar", "Euro", "Pound", "INR"].map((element) => ({
id: element,
label: element,
})),
setClientonChange() {
//have to call setCurrencyValue() here
setCurrencyValue();
},
},
},
];
const RoomDeposit = () => {
const [gridRef, setGridRef] = useState(null);
const setCurrencyValue = () => {
gridRef.current.setItemPropertyAt(2, "amount", "20");
};
return (
<ReactDataGrid
onReady={setGridRef}
columns={columns(setCurrencyValue)}
dataSource={dataSource}
/>
);
};
export default () => <RoomDeposit />;
Really I am not able to understand the question self.
we can execute global function from inside of function.
Function should be declarer first then execute.
But as per my understanding the answer can be look like this.
import React, {useState } from 'react';
import ReactDataGrid from '#inovua/reactdatagrid-community';
const RoomDeposit = () => {
const [gridRef, setGridRef] = useState(null);
const setCurrencyValue = () => {
gridRef.current.setItemPropertyAt(2, 'amount', '20')
}
function onDropdownChange(){
setCurrencyValue(); //You can execute from here
}
return (
<ReactDataGrid
onReady={setGridRef}
columns={columns}
dataSource={dataSource}
/>
);
}
export default () => <RoomDeposit />

Converting React Class component to Function Component With Hook

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.
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}.
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)
}, [])

Can't get pass the -> TypeError: Cannot read property 'id' of undefined at production build

Long story short, I have a table where I've build a searching functionality using the following code to search in the object arrays and update the state using hooks.
const SearchView = (e) => {
e.preventDefault();
console.log(e.target.value)
if (!e.target.value) {
setSearchDataSuggestions([])
setSearchField(null)
} else {
setSearchField(true)
props.setSeachingView(true)
for (const row of Object.values(rows)) {
Object.keys(row).forEach(function (item, index) {
if (typeof(row[item]) === 'string' && row[item].toLowerCase().includes(e.target.value.toLowerCase().trim())) {
setSearchDataSuggestions([...search_data_suggestions, (rows[index]) ])
}
});
}
}
}
The code works as fine in the npm start but wont work at production build ... Whenever I try to type something into the text field (where the SearchView() is called onChange) it crashes on a blank page along the following console error:
Console Error
I've no idea where this error is telling me ... Since there's no .id being passed whatsoever in my code. By any chance, I'm posting the entire code here anyways.
Can someone please help me identify where is the bug trigger and why it works fine in a dev server and not in prod !??!
Full code goes below:
import React, { useState, useEffect } from 'react';
import DataTable from 'react-data-table-component';
import moment from 'moment'
import TextField from '#material-ui/core/TextField';
import InputAdornment from '#material-ui/core/InputAdornment';
const LookUpTable = (props) => {
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [rows, setItems] = useState([]);
const [search_data_suggestions, setSearchDataSuggestions] = useState([]);
const [accessToken] = useState(props.accessToken)
const [isSearching] = useState(props.isSearching);
const [search_field, setSearchField] = useState(null);
const success_validation = row_success => {
if (row_success) {
return "True";
} else {
return "False";
}
};
const columns = [
{
name: 'Timestamp',
selector: 'timestamp',
sortable: true,
format: row => moment(row.timestamp).format('lll'),
},
{
name: 'Project ID',
selector: 'project_id',
sortable: true,
},
{
name: 'Client Name',
selector: 'client_name',
sortable: true,
right: true,
},
{
name: 'Client Email',
selector: 'client_email',
sortable: true,
right: true,
},
{
name: 'Client Mobile',
selector: 'client_mobile',
sortable: true,
},
{
name: 'Sent By',
selector: 'sent_by',
sortable: true,
},
{
name: 'Success',
selector: 'success_validation',
sortable: true,
right: true,
format: row => success_validation(row.success),
},
];
const ExpanableComponent = ({ data }) => {
return(
<div>
<p></p>
<ul>
<li><strong>Report Filename: </strong>{data.file_name} </li>
<li><strong>Project Description: </strong>{data.project_desc} </li>
<li><strong>Project Owner's Name: </strong>{data.project_owner_name} </li>
<li><strong>Project Owner's Email: </strong>{data.project_owner_email} </li>
</ul>
</div>
)
}
const SearchView = (e) => {
e.preventDefault();
console.log(e.target.value)
// setSearchValue(e.target.value.toLowerCase())
// const values = (Object.values(rows));
if (!e.target.value) {
setSearchDataSuggestions([])
setSearchField(null)
} else {
setSearchField(true)
props.setSeachingView(true)
for (const row of Object.values(rows)) {
Object.keys(row).forEach(function (item, index) {
// console.log('[' + index + '] ' + 'keys: ' + item + ' values : ' + row[item]);
if (typeof(row[item]) === 'string' && row[item].toLowerCase().includes(e.target.value.toLowerCase().trim())) {
setSearchDataSuggestions([...search_data_suggestions, (rows[index])])
// console.log('Got search as: ' + e.target.value + ' and updated with the row: ' + (rows[index]) )
}
});
}
}
}
const CustonSearchViewRender = () => {
if (search_field) {
return (
<DataTable
columns={columns}
data={search_data_suggestions}
highlightOnHover={true}
pagination={true}
fixedHeader={true}
expandableRows={true}
expandableRowsComponent={<ExpanableComponent />}
striped={true}
/>
)
}
}
// Note: the empty deps array [] means
// this useEffect will run once
// similar to componentDidMount()
useEffect(() => {
fetch("/lookup/", {
method: 'GET',
headers: {
'Authorization': `Bearer ${accessToken}`,
}
}
)
.then(res => res.json())
.then(
(result) => {
setIsLoaded(true);
setItems(result);
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
setIsLoaded(true);
setError(error);
}
)
}, [accessToken])
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<div className='lookup-table container-fluid'>
<div className="search-bar container">
<br></br>
<h4 className="search-bar-title">Past 90 days report table lookup ...</h4>
<TextField
label="Search me"
onChange={e => SearchView(e)}
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<i className="fas fa-search"></i>
</InputAdornment>
)
}}
/>
</div>
{isSearching && !search_field?
<DataTable
columns={columns}
data={rows}
highlightOnHover={true}
pagination={true}
fixedHeader={true}
expandableRows={true}
expandableRowsComponent={<ExpanableComponent />}
striped={true}
/> : null}
{search_field ? <CustonSearchViewRender/> : null}
</div>
);
}
}
export default LookUpTable;
Thank you so much.

Why does Axios keep sending requests before component mounts?

I have an app with React front and Spring backend. I use Axios to fetch from the back. I have 2 class components with tables and I can access them via a menu component (in componentDidMount and componentDidUpdate only). I use all the possible precautions against infinite loops (loaded state and isMounted with a custom name). It works in the first component which I access after logging in. However, the second component (which is logically the same as the first, just has another entity to fetch) keeps requesting with axios until i go there (i see it in the network tab of my browser). Why can it be? it is definitely not mounted and console.logs don't work from there but while I'm on first it keeps requesting on and on (and it doesn't receive anything I guess, it is 0 bytes at this time)
import React, { Component } from 'react'
import {Link} from 'react-router-dom';
import axios from 'axios'
import "react-table/react-table.css";
import ReactTable from 'react-table';
import {Button, ButtonToolbar} from 'react-bootstrap';
import { LinkContainer } from "react-router-bootstrap";
import AddCalculationsModal from './AddCalculationsModal';
import UpdateCalculationsModal from './UpdateCalculationsModal';
import Cluster from './Cluster';
import Select from 'react-select/src/Select';
export default class Calculations extends Component {
isCMounted = false;
constructor(props) {
super(props)
this.state = {
items: [],
selected: null,
addModalShow: false,
updateModalShow: false,
updateId: null,
buttonOn: false,
page: 0,
elements: 0,
loaded: false
}
}
componentDidMount() {
this.isCMounted = true;
if(!this.state.loaded){
this.load();
}
};
componentDidUpdate() {
if(!this.state.loaded){
this.load();
}
};
componentWillUnmount(){
this.isCMounted = false;
}
increasePage = () => {
this.setState({
page: this.state.page + 1
})
}
decreasePage = () => {
this.setState({
page: this.state.page - 1
})
}
load = async () => {
await axios.get(`calculations?page=${this.state.page}&elements=${this.state.elements}`)
.then(res => {
if (this.isCMounted && this.state.items.id === res.data.id){
this.setState({items: res.data})
}
});
if(this.state.selected != null && this.isCMounted) {
this.setState({buttonOn: true})
}
this.setState({loaded: true})
}
setId = (id) => {
const idValue = this.state.items[id].id;
if (this.isCMounted)
this.setState({updateId: idValue});
}
deleteRow = (id) => {
const index = this.state.items.findIndex(item => {
return item.id === this.state.items[id].id})
const idValue = this.state.items[id].id
axios.delete(`calculations/${idValue}`).then(
res => {
this.load();
}
)
this.state.items.splice(index, 1)
this.load();
}
render() {
let addModalClose = () => this.setState({addModalShow: false});
let updateModalClose = () => this.setState({updateModalShow: false});
return (
<div>
<h3>Calculations</h3>
<ReactTable
columns={
[
{
Header: "ID",
accessor: "id"
},
{
Header: "Name",
accessor: "name"
},
{
Header: "Creation Date",
accessor: "dateCreate"
},
{
Header: "Update Date",
accessor: "dateUpdate"
},
{
Header: "User",
accessor: "userId"
}
]
}
data={this.state.items}
filterable
showPagination={false}
getTrProps={(state, rowInfo) => {
if (rowInfo && rowInfo.row) {
return {
onClick: (e) => {
this.setState({
selected: rowInfo.index
})
},
style: {
background: rowInfo.index === this.state.selected ? '#00afec' : 'white',
color: rowInfo.index === this.state.selected ? 'white' : 'black'
}
}
}else{
return {}
}
}}
>
</ReactTable>
<ButtonToolbar>
<Button variant="primary" onClick={() => {
this.decreasePage();
this.load();
}}>PREVIOUS PAGE</Button>
<Button variant="primary" onClick={() => {
this.increasePage();
this.load();
}}>NEXT PAGE</Button>
</ButtonToolbar>
<ButtonToolbar>
<Button variant="primary" onClick={() => this.setState({addModalShow: true})}>
Add Calculation
</Button>
<Button variant="primary" onClick={() => {
this.setId(this.state.selected);
this.setState({updateModalShow: true})}} disabled={this.state.buttonOn ? false : true}>
Update Calculation
</Button>
<Button variant="danger" onClick={() => {
this.deleteRow(this.state.selected);
}}>DELETE</Button>
<Link to={`/calculations/${this.state.items[this.state.selected] && this.state.items[this.state.selected].id}`}>
<Button variant="warning" disabled={this.state.buttonOn ? false : true}>Cluster</Button>
</Link>
<AddCalculationsModal
show={this.state.addModalShow}
onHide={addModalClose}
calculation={this.state.items[this.state.selected]}
/>
<UpdateCalculationsModal
show={this.state.updateModalShow}
onHide={updateModalClose}
calculation={this.state.items[this.state.selected] && this.state.items[this.state.selected].id}
calcname={this.state.items[this.state.selected] && this.state.items[this.state.selected].name}
/>
</ButtonToolbar>
</div>
)
}
}
And
import React, { Component } from 'react'
import axios from 'axios'
import "react-table/react-table.css";
import ReactTable from 'react-table';
import {Button, ButtonToolbar} from 'react-bootstrap';
import AuthenticationService from '../service/AuthenticationService';
export default class Calculations extends Component {
isCMounted = false;
constructor(props) {
super(props)
this.state = {
items: [],
selected: null,
updateId: null,
loaded: false
}
}
componentDidMount() {
this.isCMounted = true;
if(!this.state.loaded) {
this.load();
}
};
componentDidUpdate() {
if(!this.state.loaded) {
this.load();
}
};
componentWillUnmount() {
this.isCMounted = false;
}
load = async () => {
if(this.isCMounted && !this.state.loaded) {
await axios.get('calculation-types')
.then(res => {
console.log(this.isCMounted)
if (this.isCMounted && this.state.items.id === res.data.id){
this.setState({items: res.data})
}
});
this.setState({loaded: true})
}
}
setId = (id) => {
const idValue = this.state.items[id].id;
if (this.isCMounted)
this.setState({updateId: idValue});
}
render() {
return (
<div>
<h3>Calculation Types</h3>
<ReactTable
columns={
[
{
Header: "ID",
accessor: "idType",
width: 100,
minWidth: 100,
maxWidth: 100
},
{
Header: "Name",
accessor: "name"
}
]
}
data={this.state.items}
filterable
showPagination={false}
getTrProps={(state, rowInfo) => {
if (rowInfo && rowInfo.row) {
return {
onClick: (e) => {
this.setState({
selected: rowInfo.index
})
},
style: {
background: rowInfo.index === this.state.selected ? '#00afec' : 'white',
color: rowInfo.index === this.state.selected ? 'white' : 'black'
}
}
}else{
return {}
}
}}
>
</ReactTable>
</div>
)
}
}
are my components. Menu is a normal link. after login i appear on the first with menu on top.
Have you tried moving this.setState({loaded: true}) into the axios response callback block? Since you're awaiting the fetch request, I wonder if the this.setState({items: res.data} that you have in the callback block is causing an infinite componentDidUpdate loop that causes load to be repeatedly called without ever having the chance to arrive at the this.setState({loaded: true}) in the final line of load.
load = async () => {
if(this.isCMounted && !this.state.loaded) {
await axios.get('calculation-types')
.then(res => {
console.log(this.isCMounted)
if (this.isCMounted && this.state.items.id === res.data.id){
this.setState({ items: res.data, loaded: true })
}
});
}
}

React dispatch is not defined remove action

i have a problem with my actionsFormatter.
When I click on the DELETE button, I get the error:
Uncaught ReferenceError: dispatch is not defined at onClick
How could I fix this problem?
import { removeEnvironnement } from '../../actions/environnement';
const EnvironnementList = (props) => (
<BootstrapTable
keyField='id'
data={ props.store.environnements }
columns={ columns }
selectRow={selectRow}
pagination={ paginationFactory() }
filter={ filterFactory() }
striped hover condensed
/>
);
const actionsFormatter = (cell, row) => {
const id=row.id
return (
<button className="btn btn-danger"
onClick={() => {
dispatch(removeEnvironnement({ id }));}}
>Delete</button>
);
};
const columns = [{
dataField: 'id',
text: 'ID'
}, {
dataField: 'nom',
text: 'Nom',
filter: textFilter()
}, {
dataField: 'actions',
text: 'Action',
formatter: actionsFormatter
} ];
const selectRow = {
mode: 'checkbox',
clickToSelect: true,
bgColor: '#00BFFF'
};
const mapStateToProps = (state) => {
return {
store: state
};
}
export default connect(mapStateToProps)(EnvironnementList);
Here is my code to do the remove :
Should I remove the dispatch part?
const _removeEnvironnement = ({ id } = {}) => ({
type: 'REMOVE_ENVIRONNEMENT',
id
});
export const removeEnvironnement = ({ id } = {}) => {
return (dispatch) => {
return axios.delete(`environnements/${id}`).then(() => {
dispatch(_removeEnvironnement({ id }));
})
}
};
What is dispatch in your actionsFormatter? It is defined neither on actionsFormatter scope nor on out of actionsFormatter scope. That's the problem and that's the javascript interpreter talking you about.
One of the possible fix is to import you redux store
store.js
export const store = createStore(...)
EnvironmentList.js
import { store } from './path/to/store.js'
// ...
const actionsFormatter = (cell, row) => {
const { dispatch } = store
const id = row.id
// ...
};
This way you'll get dispatch available in actionsFormatter body.
Another way is to provide mapped method via connect -> EnvironmentList -> actionsFormatter chain. Do what Arnaud Christ suggested in his reply and then refactor the code:
const EnvironmentList = (props) => (
<BootstrapTable
keyField='id'
data={ props.store.environnements }
columns={ columns(props.removeEnvironment) }
selectRow={selectRow}
pagination={ paginationFactory() }
filter={ filterFactory() }
striped hover condensed
/>
);
const actionsFormatter = (removeEnvironment) => (cell, row) => {
const id=row.id
return (
<button className="btn btn-danger"
onClick={() => {
removeEnvironment({ id });
}}
>Delete</button>
);
};
const columns = (removeEnvironment) => [{
dataField: 'id',
text: 'ID'
}, {
dataField: 'nom',
text: 'Nom',
filter: textFilter()
}, {
dataField: 'actions',
text: 'Action',
formatter: actionsFormatter(removeEnvironment)
} ];
So, the connected EnvironmentList got necessary removeEnvironment method on it's props. Then we passes it to columns creator, which passed it to actionsFormatter creator.
You have to link your component with the dispatch method.
As you are already using react-redux to connect your component to your Redux store, you can easily do that through mapping dispatch to props.
Just add the following:
const mapStateToProps = (state) => {
return {
store: state
};
}
const mapDispatchToProps = dispatch => {
return {
removeEnvironnement: id => {
dispatch(removeEnvironnement({ id }));
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(EnvironnementList);
And then in your onClick handler, just call this.props.removeEnvironnement(id)

Categories

Resources