Converting React Class component to Function Component With Hook - javascript

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

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 Class Component to Functional Component With Hooks

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>
)
}

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)

how to setState in componentDidMount in promise without getting setState error

I'm calling an api and setting the state with the response.
I'm not able to call setState without seeing this error:
The error does not occur if setState happens outside the promise.
How do you set an API response to state without seeing this error?
Can't call setState (or forceUpdate) on an unmounted component
componentDidMount() {
const url = 'https://localhost:8000/api/items'
let items;
fetch(url, {mode: 'cors'})
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
Entire component for reference:
import React, { Component } from 'react'
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import GridList from '#material-ui/core/GridList';
import GridListTile from '#material-ui/core/GridListTile';
import GridListTileBar from '#material-ui/core/GridListTileBar';
import ListSubheader from '#material-ui/core/ListSubheader';
import IconButton from '#material-ui/core/IconButton';
import InfoIcon from '#material-ui/icons/Info';
const styles = theme => ({
root: {
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'space-around',
overflow: 'hidden',
backgroundColor: theme.palette.background.paper,
},
icon: {
color: 'rgba(255, 255, 255, 0.54)',
},
});
class ItemList extends Component {
constructor(props) {
super(props)
this.state = {
items: [],
isLoaded: false,
}
this.handleItemClick = this.handleItemClick.bind(this);
}
componentDidMount() {
const url = 'https://localhost:8000/api/items'
let items;
fetch(url, {mode: 'cors'})
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
handleItemClick(id) {
}
handleRenderItems() {
const { classes } = this.props
const { items } = this.state;
return this.state.items.map((item, idx) => {
const id = item.id;
return (
<GridListTile onClick={() => this.handleItemClick(id)} key={idx}>
<img src={item.key_image} alt={item.title} />
<GridListTileBar
title={item.title}
subtitle={<span>${item.rent_price_day}/day</span>}
actionIcon={
<IconButton className={classes.icon}>
<InfoIcon />
</IconButton>
}
/>
</GridListTile>
)
})
}
render() {
const { classes } = this.props;
return (
<div className={classes.root}>
<GridList cols={3} cellHeight={220} className={classes.gridList}>
<GridListTile key="Subheader" cols={3} style={{ height: 'auto' }}>
<ListSubheader component="div">Popular Items</ListSubheader>
</GridListTile>
{this.handleRenderItems()}
</GridList>
</div>
);
}
}
ItemList.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(ItemList);
Instead of handling this in the component - I think it may be simpler to handle the call to the API in an action creator, and use redux to store the data - then just send it to the component when it's available. Better to keep these details out of the view anyway.
Also, wouldn't typically use a style object in the view either - but it's boilerplate with material-ui.
Add below inside your component,
componentDidMount() {
this.isMounted = true;
}
componentWillUnmount() {
this.isMounted = false;
}
Then setState only if this.isMounted is true. This should solve your problem, but this is not a recommended pattern. Please refer https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html
Check the code of parent component which holds the <ItemList/>. If you are rendering <ItemList/> based on certain conditions, the problem might be in those conditions.
It simple. You just have to return the value of the second promise in a variable.
Like this :
let items;
let errorFetch;
fetch(url, {mode: 'cors'})
.then(res => res.json())
.then(
(result) => {
items = result;
},
(error) => {
errorFetch = error
}
);
this.setState(items ? { isLoaded : true, items } : { isLoaded : true, error: errorFetch });

Categories

Resources