Call actions in componentdidUpdate - reactjs

I am trying to update state in componentdidUpdate and for this I want to call a function but the console shows a typeError:
Uncaught (in promise) TypeError: this.props.updateMarketCap is not a function
I have imported that func in my file..below is an example:
import { fetchMarketCap } from '../Actions/Marketcap';
import { updateMarketCap } from '../Actions/Marketcap';
componentDidMount(){
// setInterval(this.props.fetchMarketCap(), 3000);
this.props.fetchMarketCap();
this.interval = setInterval(() => {
this.props.fetchMarketCap();
}, 20000);
}
componentDidUpdate(prevProps, prevState){
const prev = prevProps.marketcap.map((coin, i) => (
<tr key={this.props.marketcap[i].CoinInfo.Id}>
<td className="crypt-up"><b>{this.props.marketcap[i].DISPLAY.USD.MKTCAP}</b></td>
<td className={coin.DISPLAY.USD.PRICE < this.props.marketcap[i].DISPLAY.USD.PRICE ? 'crypt-up' : (coin.DISPLAY.USD.PRICE > this.props.marketcap[i].DISPLAY.USD.PRICE ? 'crypt-down' : 'equal')}>{this.props.marketcap[i].DISPLAY.USD.PRICE}>{this.props.marketcap[i].DISPLAY.USD.PRICE}</td>
</tr>
));
this.props.updateMarketCap(prev);
}
And at the end file:
const mapStateToProps = state => ({
marketcap: state.marketcap.coins
});
export default connect ( mapStateToProps, { fetchMarketCap } )(Marketcap);
And the action function is
export const updateMarketCap = (newData) => dispatch => {
dispatch({
type: UPDATE_MARKET_CAP,
payload: newData
})
}
I have imported Action types and other things properly

Something has to be wrong in connect function i think. The mapDispatchToProps is not defined properly and this is the wire that connect your actions imported with the props you are trying to call.
mapDispatchToProps = {
fetchMarketCap ,
updateMarketCap
};

You need to have componentDidUpdate return a true or false, so in your situation maybe something like. I'm assuming you have a mapDispatchToProps on this page right?
componentDidUpdate(prevProps, prevState){
if(prevProps!==prevState){
let marketCap
marketCap = (
const prev = prevProps.marketcap.map((coin, i) => (
<tr key={this.props.marketcap[i].CoinInfo.Id}>
<td className="crypt-up"><b>{this.props.marketcap[i].DISPLAY.USD.MKTCAP}</b></td>
<td className={coin.DISPLAY.USD.PRICE < this.props.marketcap[i].DISPLAY.USD.PRICE ? 'crypt-up' : (coin.DISPLAY.USD.PRICE > this.props.marketcap[i].DISPLAY.USD.PRICE ? 'crypt-down' : 'equal')}>{this.props.marketcap[i].DISPLAY.USD.PRICE}>{this.props.marketcap[i].DISPLAY.USD.PRICE}</td>
</tr>
));
}
this.props.updateMarketCap(prev);
}
)
Then in your return statement throw {marketCap} in where you want it

Related

Component doesnt rerender on state change

My problem is that my component doesnt rerender, when my state changes. I am managing my state in a custom Hook and after an put request to my backend my state gets updated. This works completely fine, but the content of my page doesnt get refreshed when changing my sate after the put request.
Component:
import React, { useEffect, useState } from 'react';
import { CONTROLLERS, useBackend } from '../../hooks/useBackend';
import Loading from '../Alerts/loading';
import {Table} from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import DropdownForm from '../Forms/dropdown';
function AdminPanel() {
const headers = ['ID', 'Title', 'Release Date', 'Producer', 'Director', 'Status', 'UTC Time', '#', '#'];
const [error, setError] = useState(false);
const [loaded, setLoaded] = useState(false);
const [requests, backend] = useBackend(error, setError);
useEffect(() => {
backend(CONTROLLERS.REQUESTS.getRequestsAdmin());
}, [])
useEffect(() => {
setLoaded(requests !== undefined);
console.log(requests);
}, [requests])
const handleUpdate = (e, result) => {
backend(CONTROLLERS.REQUESTS.put({requestStatus: result, accessToken: localStorage.accessToken}, e));
}
if(!loaded) return <Loading/>
if(error) return <p>No Access</p>
return(
<>
<DropdownForm items={['A-Z', 'Z-A', 'None']} title={'Filter'} first={2} setHandler={setFilter}/>
<DropdownForm items={['+20', '+50', 'All']} title={'Count'} first={0} setHandler={setCount}/>
{/* <DropdownForm/> */}
<Table bordered hover responsive="md">
<thead>
<tr>
{headers.map((item, index) => {
return( <th className="text-center" key={index}>{item}</th> );
})}
</tr>
</thead>
<tbody>
{requests.map((item, index) =>{
return(
<tr>
<td>{index + 1}</td>
<td>{item.movie.movieTitle}</td>
<td>{item.movie.movieReleaseDate}</td>
<td>{item.movie.movieProducer}</td>
<td>{item.movie.movieDirector}</td>
<td>{(item.requestStatus === 1 ? 'Success' : item.requestStatus ===2 ? 'Pending' : 'Denied')}</td>
<td className="col-md-3">{item.requestDate}</td>
{/* <td><span onClick={() => handleDelete(item.requestID)}><i className="fas fa-times"></i></span></td> */}
<td><span onClick={() => handleUpdate(item.requestID, 3)}><i className="fas fa-times"></i></span></td>
<td><span onClick={() => handleUpdate(item.requestID, 1)}><i className="fas fa-check"></i></span></td>
</tr>);
})}
</tbody>
</Table>
</>
);
}
// }
export default AdminPanel;
customHook:
import axios from "axios";
import { useEffect, useRef, useState } from "react";
import notify from "../Components/Alerts/toasts";
const BASE_URL = 'https://localhost:44372/api/';
const R = 'Requests/'; const M = 'Movies/'; const U = 'Users/';
const buildParams = (url, type, header, param) => {
return {url: url, type: type, header: header, param: param};
}
export const CONTROLLERS = {
REQUESTS: {
getRequestsAdmin: () => buildParams(`${R}GetRequestsAdmin`, 'post', true, {accessToken:
}
export const useBackend = (error, setError) => {
const [values, setValues] = useState([]);
async function selectFunction(objc) {
switch(objc.type) {
case 'put': return buildPutAndFetch(objc.url, objc.param, objc.header);break;
default: console.log("Error in Switch");
}
}
async function buildPutAndFetch(url, param, header) {
const finalurl = `${BASE_URL}${url}`;
return axios.put(finalurl, param, {headers: {
'Authorization': `Bearer ${(localStorage.accessToken)}`
}})
.then(res => {
if(res.data && 'accessToken' in res.data) localStorage.accessToken = res.data.accessToken;
else {
//When an object gets updated, the backend returns the updated object and replaces the old one with the //new one.
const arr = values;
const found = values.findIndex(e => e[(Object.keys(res.data))[0]] == res.data.requestID);
arr[found] = res.data;
setValues(arr);
}
setError(false);
return true;
})
.catch(err => {
setError(true);
return false;
})
}
}
function response(res) {
setValues(res.data)
setError(false);
}
return [values,
async (objc) => selectFunction(objc)];
}
It's likely due to the fact that your buildPutAndFetch function is mutating the values array in state, rather than creating a new reference. React will bail out on state updates if the reference doesn't change.
When you declare your arr variable, it's setting arr equal to the same reference as values, rather than creating a new instance. You can use the spread operator to create a copy: const arr = [...values].
It's also worth noting that because this is happening asynchronously, you may want to use the function updater form of setValues to ensure you have the most current set of values when performing the update.
setValues(prev => {
const arr = [...prev];
const found = prev.findIndex((e) => e[Object.keys(res.data)[0]] == res.data.requestID);
arr[found] = res.data;
return arr;
});

ReactJS state updates one step behind

I am trying to create a simple table using ReactJS to display user information. Here's how the general code structure looks like:
class ParentComponent extends React.Component {
state = {
data : []
}
componentDidMount() {
// initializes state with data from db
axios.get("link/").then(res => {
this.setState({data: res.data});
});
// I should be able to call this.getData() instead
// of rewriting the axios.get() function but if I do so,
// my data will not show up
}
// retrieves array of data from db
getData = () => {
axios.get("link/").then(res => {
this.setState({data: res.data});
});
}
render() {
return (
<div>
<ChildComponent data={this.state.data} refetch={this.getData} />
</div>
)
}
}
Each of the generated rows should have a delete function, where I'll delete the entry from the database based on a given id. After the deletion, I want to retrieve the latest data from the parent component to be redisplayed again.
class ChildComponent extends React.Component {
// deletes the specified entry from database
deleteData = (id) => {
axios.get("deleteLink/" + id).then(res => {
console.log(res);
// calls function from parent component to
// re-fetch the latest data from db
this.props.refetch();
}).catch(err => {console.log(err)});
}
render() {
let rows = null;
if(this.props.data.length) {
// map the array into individual rows
rows = this.props.data.map(x => {
return (
<tr>
<td>{x.id}</td>
<td>{x.name}</td>
<td>
<button onClick={() => {
this.deleteData(x.id)
}}>
Delete
</button>
</td>
</tr>
)
})
}
return (
<div>
<table>
<thead></thead>
<tbody>
{rows}
</tbody>
</table>
</div>
)
}
}
The two problems which I encountered here are:
Logically, I should be able to call this.getData() from within componentDidMount(), but if I do so, the table doesn't load.
Whenever I try to delete a row, the table will not reflect the update even though the entry is removed from the database. The table will only be updated when I refresh the page or delete another row. Problem is, the component is always lagging behind by 1 update.
So far I have tried:
this.forceUpdate() - doesn't work
this.setState({}) - empty setState doesn't work either
changing componentDidMount() to componentDidUpdate() - error showing that I have "reached maximum depth" or something along that line
adding async await in front of axios - doesn't work
Any help is appreciated. Thanks in advance.
EDIT: I did some debugging and tracked down the issue, which is not relevant to my question. My deleteData() which is located in ChildComponent uses axios.post() instead of axios.get(), which I overlooked.
deleteData = (id) => {
axios.post("deleteLink/", id).then(res => {
console.log(res);
// calls function from parent component to
// re-fetch the latest data from db
this.props.refetch();
}).catch(err => {console.log(err)});
}
In order for axios.post() to return a response, in order to perform .then(), you'll need to add a res.json() to the routing codes.
You should map data into your child.
Change your parent like this:
class ParentComponent extends React.Component {
state = {
data : []
}
componentDidMount() {
this.getData();
}
getData = () => axios.get("link/").then(res => this.setState({data: res.data});
deleteData = (id) => axios.get("deleteLink/" + id).then(res => this.getData())
.catch(err => { console.log(err) });
render() {
return (
<div>
<table>
<thead></thead>
<tbody>
{this.state.data.map(x => <ChildComponent row={x} deleteData={this.deleteData} />)}
</tbody>
</table>
</div>
)
}
}
And your child component should be like this
const ChildComponent = ({row,deleteData}) => (
<tr>
<td>{row.id}</td>
<td>{row.name}</td>
<td><button onClick={() => deleteData(row.id)}>Delete</button></td>
</tr >
)
I can't find an issue in your code, the only way I can help is to tell you how I would debug it.
edit parent like so:
class ParentComponent extends React.Component {
state = {
data : []
}
componentDidMount() {
// You are right when you say this should works, so
// stick to it until the bug is fixed
console.log("parent mounted");
this.getData();
}
// retrieves array of data from db
getData = () => {
console.log("fetching data");
axios.get("link/").then(res => {
console.log("fetched data", res.data);
this.setState({data: res.data});
});
}
render() {
return (
<div>
<ChildComponent
data={this.state.data}
refetch={this.getData}
/>
</div>
)
}
}
and in the child component add these 2 lifecycle methods just for debugging purposes:
componentDidMount () {
console.log("child mounted", this.props.data)
}
componentDidUpdate (prevProps) {
console.log("old data", prevProps.data);
console.log("new data", this.props.data);
console.log("data are equal", prevProps.data === this.props.data);
}
if you can share the logs I can try help you more

React - Passing state as props not causing re-render on child component

I have a parent component that initiates state and then once mounted updates it from the results of a get request
const [vehicles, handleVehicles] = useState([])
useEffect(() => {
const token = localStorage.getItem('token')
axios({
//get data from backend
}).then(({data}) => {
handleVehicles(prevState => [...prevState, data])
}).catch((err) => console.log(err))
}, [])
I have the state passed down as a prop into a child component. In my child component I run a check to see if the vehicles array is populated...if it is I return some jsx otherwise I return nothing. My issue is that the state change won't reflect in the prop passed down and cause a re-render. It remains at an empty array unless I refresh the page.
I pass it down via
<RenderTableData vehicles={vehicles} />
My child component is:
const RenderTableData = (props) => {
if (!props.vehicles[0]) {
return null
} else {
return (
props.vehicles[0].map((vehicle) => {
return (
<tr key={vehicle._id}>
<td>{vehicle.name}</td>
<td>{vehicle._id}</td>
<td><button className="has-background-warning">Edit</button></td>
<td><button className="has-background-danger">Remove</button></td>
</tr>
)
})
)
}
}
How would I approach solving this?
Edit - It does actually work as is...For some reason the http request takes an age to return the data (and I was never patient enough to notice)...So I have a new problem now :(
I don't know what exactly is prevState but I think your problem is caused by passing to handleVehicles a function instead of the new value. So your code should be:
const [vehicles, handleVehicles] = useState([])
useEffect(() => {
const token = localStorage.getItem('token')
axios({
//get data from backend
}).then(({data}) => {
handleVehicles([...prevState, data])
}).catch((err) => console.log(err))
}, [])
Why you are using the map function on the object. Your child component should be like below:
const RenderTableData = (props) => {
if (!props.vehicles[0]) {
return null
} else {
return (
props.vehicles.map((vehicle) => {
return (
<tr key={vehicle._id}>
<td>{vehicle.name}</td>
<td>{vehicle._id}</td>
<td><button className="has-background-warning">Edit</button></td>
<td><button className="has-background-danger">Remove</button></td>
</tr>
)
})
)
}
}
I wrote a working example at CodeSandbox. Some comments:
Your effect will run just once, after the component mounts.
If the API returns successfully, a new vehicle list is created with the previous one. But prevState is empty, so this is the same as handleVehicles(data) in this case. If you wanna spread data inside the vehicle list, don't forget to handleVehicles(prevState => [...prevState, ...data]);
useEffect(() => {
const token = localStorage.getItem('token')
axios({
//get data from backend
}).then(({data}) => {
handleVehicles(prevState => [...prevState, data])
}).catch((err) => console.log(err))
}, [])
In your children component, you probably want to map over the vehicles list, not over the first element. So, you should remove the [0] in
const RenderTableData = (props) => {
if (!props.vehicles[0]) {
return null
} else {
return (
props.vehicles[0].map((vehicle) => {
return (
...
)
})
)
}
}

JSON Array mapping in ReactJS from request

Currently i'm rewriting a class component to a function component. I need to do this since i need to use the useSelector hook from redux. Now i'm getting pretty close but i'm having some trouble with the json array getting mapped. It's letting me know it's not a function. In the fetch i'm logging the leaderboard which has returned. This gives me the json i was expecting.
[
{
"ID": 1,
"teamName": "Developers",
"time": "19:54"
},
{
"ID": 1591621934400,
"teamName": "h435hfg",
"time": "19:54"
}
]
Then here is my code that im having trouble with:
import React, {useEffect, useState} from 'react';
import '../style/App.scss';
import {useSelector} from "react-redux";
function Leaderboard() {
const io = require('socket.io-client');
const socket = io.connect("http://localhost:3001/", {
reconnection: false
});
const [leaderboard, setLeaderboard] = useState([]);
const timerState = useSelector(state => state.timerState);
useEffect(() => {
socket.emit("addTeamToLeaderboard", getTeam());
fetch('http://localhost:3000/leaderboard')
.then(response => response.json())
.then(leaderboard => {
leaderboard.push(getTeam()); // this is just so your team score renders the first time
setLeaderboard({leaderboard})
console.log(leaderboard)
});
}, [socket]);
const getTeam = () => {
let team = JSON.parse(sessionStorage.getItem('currentTeam')) ;
team.time = timerState;
return team;
}
const leaderboardElements = leaderboard.map((data, key) => {
return (
<tr key={key} className={ data.ID === getTeam().ID ? "currentTeam" : "" }>
<td>{data.teamName}</td>
<td>{data.time}</td>
</tr>
)
})
return (
<div>
<h1>Leaderboard</h1>
<table className="leaderboard">
<tr>
<th>Team</th>
<th>Time</th>
</tr>
{leaderboardElements}
</table>
</div>
);
}
export default Leaderboard;
The old code which im rewriting:
import React from 'react';
import '../style/App.scss';
class Leaderboard extends React.Component {
state = {
leaderboard: []
}
compare(a, b) {
if (a.time < b.time) {
return -1;
}
if (a.time > b.time) {
return 1;
}
return 0;
}
getTeam(){
let team = JSON.parse(sessionStorage.getItem('currentTeam')) ;
team.time = 12.13; //Todo add actual playing time
return team;
}
componentDidMount() {
const io = require('socket.io-client');
const socket = io.connect("http://localhost:3001/", {
reconnection: false
});
socket.emit("addTeamToLeaderboard", this.getTeam());
fetch('http://localhost:3000/leaderboard')
.then(response => response.json())
.then(leaderboard => {
leaderboard.push(this.getTeam()); // this is just so your team score renders the first time
this.setState({ leaderboard })
});
}
render() {
return (
<div>
<h1>Leaderboard</h1>
<table className="leaderboard">
<tr>
<th>Team</th>
<th>Time</th>
</tr>
{
this.state.leaderboard.sort(this.compare).map((data, key) => {
return (
<tr key={key} className={ data.ID == this.getTeam().ID ? "currentTeam" : "" }>
<td>{data.teamName}</td>
<td>{data.time}</td>
</tr>
)
})
}
</table>
</div>
);
}
}
export default Leaderboard;
I'm not following why you are changing leaderboard data type. If it is an array you shouldn't do setLeaderboard({leaderboard}) because you are assigning an object to the state.
You should pass a new array to the setLeaderboard like:
setLeaderboard([...leaderboard]);
Also if you do
setLeaderboard([...leaderboard]);
console.log(leaderboard);
You will not get the updated state right in the log, because set state is an asynchronous call.
Another tip, I would highly recommend you to put the socket connection not in the useEffect function, put outside the functional component.
const io = require('socket.io-client');
const socket = io.connect("http://localhost:3001/", {
reconnection: false
});
function Leaderboard() {
...
}
It's letting me know it's not a function
/* fetch data */
leaderboard.push(getTeam());
setLeaderboard({leaderboard}) // => change to setLeaderboard(leaderboard.concat(getTeam()))
console.log(leaderboard)
/* other functions below */
the difference between setState and the setLeaderboard that is returned from useState is that (when giving none callback argument)
setState expects an object with {[key: stateYouAreChanging]: [value: newState],
setLeaderboard expects the newStatValue as the argument.
So your code above is setting leaderboard state to be an object with that looks like this
leaderboard = {
leaderboard: NEW_LEADERBOARD_FETCHED_FROM_REQUEST
}

redux unnecessarily rerenders whole component

the problem is as follows:
this is what i have in my redux duck
export const myInit = () => (dispatch, getState) => {
db.ref(`/parts/`).on(
'value',
(snapshot) =>
(dispatch(getParts(mapObjectToArray(snapshot.val())))))
}
export const addAmount = (objecter) => (dispatch, getState) => {
let findKey = getState().partsState.parts.find(x => {
if (x.part === objecter)
return x.key
})
let xAmount
getState().partsState.parts.find(x => {
if (x.part === objecter)
return xAmount = x.amount
})
db.ref(`/parts/${findKey.key}/amount`).set(xAmount + 1)
}
myInit() is called once in the store so the parts are saved in the state.
I display those parts in my component in a table. In each tr there is a name of part, amount and a button to change amount of parts. Once you click the button, the amount of the given part changes. Everything seems to be working just fine unless, the amount change is called when parts are filtered with state of an input value.
When list is filtered, and amount of part is changed, the whole component rerenders. Search field is cleared and the list extends. I don't know what to do. Please tell me what am I missing.
the component:
class ListOfParts extends Component {
state = {
basicSearchInput: '',
ITEMS_PER_PAGE: 10,
currentPage: 0,
parts: []
}
setStateForSearch(event) {
this.setState({basicSearchInput: event.target.value})
}
render() {
let myArrayForState = ['actuator', 'back_plate']
let arrayForHeadings = ['Actuator', 'Back plate'
]
let parts = this.props.parts;
const filter = parts
.filter(part => {
return (part.part.toLowerCase().includes(this.state.basicSearchInput.toLowerCase()))
|| (part.group.toLowerCase().includes(this.state.basicSearchInput.toLowerCase()))
}
)
const numberOfParts = filter && filter.length
return (
<div>
<TextField
type={"search"}
value={this.state.basicSearchInput}
onChange={event => this.setStateForSearch(event)}
/>
<RaisedButton
onClick={this.handleOpen}
>
add
</RaisedButton>
<Row>
<table>
<tbody>
{filter && filter.length ?
filter.filter((el, i) => {
return (i >= this.state.ITEMS_PER_PAGE * this.state.currentPage
&&
i < this.state.ITEMS_PER_PAGE * (this.state.currentPage + 1))
}).map((partInStateArray, index) =>
<tr id={`${partInStateArray.part}`}
key={`${partInStateArray.part}${this.props.index}`}>
<td>{this.arrayForHeadings[this.myArrayForState.indexOf(partInStateArray.group)]} {partInStateArray.part}</td>
<td>{partInStateArray.amount}</td>
<td>
<button onClick={this.add}>+</button>
</td>
</tr>)
)
: 'loading'
}
</tbody>
</table>
</Row>
<div style={{textAlign: 'center'}}>
<Pagination
total={Math.ceil(numberOfParts / this.state.ITEMS_PER_PAGE)}
current={this.state.currentPage + 1}
display={10}
onChange={newPage => this.setState({currentPage: newPage - 1})}
/>
</div>
</div>
)
}
}
const mapStateToProps = state => ({
parts: state.partsState.parts,
})
const mapDispatchToProps = dispatch => ({
addAmount: (objectToAdd) => dispatch(addAmount(objectToAdd)),
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(ListOfParts)

Resources