React API call in componentDidMount, and componentWillReceiveProps (order confusion) - reactjs

I'm fairly new to React and Redux. I'm having this issue where the ratings sometimes don't show up when I refresh the page (please see screenshot). I think it's because sometimes the user from Redux comes into componentWillReceiveProps before loadTeamsData executes, but I don't know why that would make ratings not show up. (Also, I feel like my code is crap... Any critic is appreciated!)
Home.js
export class Home extends React.Component {
state = {
upVote: null,
downVote: null,
clickedTeam: "",
teams: teams
};
// When component mounted, add in thumbUp & thumbDown properties to each team
componentDidMount() {
const { user } = this.props.auth;
console.log("didmount");
this.loadTeamsData();
// Stores user voting info to state when coming from a different page
if (user) {
if (!(Object.entries(user).length === 0)) {
console.log("user", user, user.upVote, user.downVote);
this.setState({
upVote: user.upVote,
downVote: user.downVote
});
}
}
}
// Loads teams thumbUp, thumbDown data to state
loadTeamsData = () => {
axios.get("/api/teams/").then(res => {
console.log("data", res.data);
this.setState({
teams: this.state.teams.map(team => {
res.data.map(vote => {
if (vote.id === team.id) {
team.thumbUp = vote.thumbUp;
team.thumbDown = vote.thumbDown;
}
return vote;
});
return team;
})
});
});
};
// When props from Redux come in, set the state
UNSAFE_componentWillReceiveProps(nextProps) {
const { user } = nextProps.auth;
if (user !== this.props.auth.user && user) {
console.log("willreceiveprops", `\n`, this.props.auth.user, user);
this.setState({
upVote: user.upVote,
downVote: user.downVote
});
}
}
// Handle click on thumbs
onClickHandler = (id, e) => {
const { alert } = this.props;
const up = e.target.classList.contains("up");
if (this.props.auth.isAuthenticated) {
if (up && this.state.upVote === "") {
if (id === this.state.downVote) {
alert.error("You cannot up vote and down vote the same team!");
} else {
this.props.update_up(id);
this.setState(prevState => {
return {
teams: prevState.teams.map(team => {
if (id === team.id) {
team.thumbUp = team.thumbUp + 1;
team.votedUpColor = { color: "#1E95E0" };
}
return team;
}),
clickedTeam: id,
upVote: id
};
});
alert.show(`You Up Voted ${id}`);
}
} else if (!up && this.state.downVote === "") {
if (id === this.state.upVote) {
alert.error("You cannot up vote and down vote the same team!");
} else {
this.props.update_down(id);
this.setState(prevState => {
return {
teams: prevState.teams.map(team => {
if (id === team.id) {
team.thumbDown = team.thumbDown + 1;
team.votedDownColor = { color: "#F8004C" };
}
return team;
}),
clickedTeam: id,
downVote: id
};
});
alert.show(`You Down Voted ${id}`);
}
} else {
alert.show("You have already voted.");
}
} else {
alert.show("Please log in first!");
this.props.history.push(`/login`);
}
};
// When user votes, update the db before updating the state
UNSAFE_componentWillUpdate(newProps, newState) {
newState.teams.map(team => {
if (team.id === newState.clickedTeam) {
axios.put(`/api/teams/${newState.clickedTeam}/`, {
id: team.id,
thumbUp: team.thumbUp,
thumbDown: team.thumbDown
});
}
});
}
render() {
// Welcome header message when user logs in
console.log("render", this.state.teams[0].thumbUp);
const { isAuthenticated, user } = this.props.auth;
const { upVote, downVote } = this.state;
const welcome_header = (
<div className="welcome-header">
<h4 style={{ textAlign: "left" }} className="welcome-header-line">
Welcome, {user && user.username}!
</h4>
<h4 style={{ textAlign: "left" }} className="welcome-header-line">
<span>
Your Vote:{" "}
<i className="far fa-thumbs-up up" style={{ color: "#1E95E0" }}></i>
<span style={{ textTransform: "capitalize" }}>{upVote}</span>
</span>{" "}
<span>
<i
className="far fa-thumbs-down down"
style={{ color: "#F8004C" }}
></i>
<span style={{ textTransform: "capitalize" }}>{downVote}</span>
</span>
</h4>
</div>
);
return (
<div className="home">
<div className="home-container">
{isAuthenticated && welcome_header}
<h2>Who Is Your NBA Champion This Year?</h2>
<Teams
upVote={this.state.upVote}
downVote={this.state.downVote}
teams={this.state.teams}
onClickHandler={this.onClickHandler}
/>
</div>
</div>
);
}
}
Home.propTypes = {
update_up: PropTypes.func.isRequired,
update_down: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth
});
export default connect(mapStateToProps, { update_up, update_down })(
withAlert()(withRouter(Home))
);
When ratings show up
When ratings don't show up
I'm console logging this.state.teams[0] and this.state.teams[0].thumbUp in the render method.
Even during the first render, both thumbUp and thumbDown show in this.state.teams[0], but this.state.teams[0].thumbUp appears to be undefined.

I happened to fix the issue. The issue was actually in the Rating.js file, sorry that I didn't post that file cuz I thought the issue had to be in Home.js.
Before, in Rating.js file, I originally brought in user from redux which caused the issue (I believe it didn't make Rating re-render when in Home.js the axios get call happened after componentWillReceiveProps).
After, instead of bringing in user from redux, I passed user to Rating.js as a prop from Home.js.
Even though it works fine now, I still don't know what exactly the issue was... I'd much appreciate it if someone could enlighten me! Also, please critique my code (i.e. where I can improve)! THANK YOU!
Rating.js (Before)
import React from "react";
import "./Rating.css";
import PropTypes from "prop-types";
import { connect } from "react-redux";
const Rating = props => {
const { thumbUp, thumbDown, id, votedUpColor, votedDownColor } = props.team;
const { upVote, downVote, onClickHandler } = props;
const { user } = props.auth;
let thumbUpColor =
user && id === upVote ? { color: "#1E95E0" } : votedUpColor;
let thumbDownColor =
user && id === downVote ? { color: "#F8004C" } : votedDownColor;
console.log(id, thumbUp, thumbDown);
return (
<div className="rating" key={id}>
<button
className="thumb-up up"
style={thumbUpColor}
onClick={e => onClickHandler(id, e)}
>
<i className="far fa-thumbs-up up"></i>
<span style={{ userSelect: "none" }} className="up">
{thumbUp}
</span>
</button>
<button
className="thumb-down down"
style={thumbDownColor}
onClick={e => onClickHandler(id, e)}
>
<i className="far fa-thumbs-down down"></i>
<span style={{ userSelect: "none" }} className="down">
{thumbDown}
</span>
</button>
</div>
);
};
Rating.propTypes = {
team: PropTypes.object.isRequired,
onClickHandler: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth
});
export default connect(mapStateToProps)(Rating);
Rating.js (After)
import React from "react";
import "./Rating.css";
import PropTypes from "prop-types";
// import { connect } from "react-redux";
const Rating = props => {
const { thumbUp, thumbDown, id, votedUpColor, votedDownColor } = props.team;
const { upVote, downVote, onClickHandler, user } = props;
// const { user } = props.auth;
let thumbUpColor =
user && id === upVote ? { color: "#1E95E0" } : votedUpColor;
let thumbDownColor =
user && id === downVote ? { color: "#F8004C" } : votedDownColor;
console.log(id, thumbUp, thumbDown);
return (
<div className="rating" key={id}>
<button
className="thumb-up up"
style={thumbUpColor}
onClick={e => onClickHandler(id, e)}
>
<i className="far fa-thumbs-up up"></i>
<span style={{ userSelect: "none" }} className="up">
{thumbUp}
</span>
</button>
<button
className="thumb-down down"
style={thumbDownColor}
onClick={e => onClickHandler(id, e)}
>
<i className="far fa-thumbs-down down"></i>
<span style={{ userSelect: "none" }} className="down">
{thumbDown}
</span>
</button>
</div>
);
};
Rating.propTypes = {
team: PropTypes.object.isRequired,
onClickHandler: PropTypes.func.isRequired
// auth: PropTypes.object.isRequired
};
// const mapStateToProps = state => ({
// auth: state.auth
// });
export default Rating;

Related

I am not able to change state and pass props

I have the stake component that is rendered 4 times in the parent class component. I am trying to pass valueNewStake as prop to its parent component and group all the inputs in one common array (see allStakes). For a reason I am not able to change the state and also the dom does not render the button next to the component. Can anyone explain me why it is happening as I am new in react. Thanks
import React, { Component } from 'react';
import Stake from './stake';
class FetchRandomBet extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
bet: null,
value: this.props.value,
allStakes: ['']
};
}
async componentDidMount() {
const url = "http://localhost:4000/";
const response = await fetch(url);
const data = await response.json();
this.setState({
loading: false,
bet: data.bets,
});
}
render() {
const { valueProp: value } = this.props;
const { bet, loading } = this.state;
if (loading) {
return <div>loading..</div>;
}
if (!bet) {
return <div>did not get data</div>;
}
return (
< div >
{
loading || !bet ? (
<div>loading..</div>
) : value === 0 ? (
<div className="bet-list">
<ol>
<p>NAME</p>
{
bet.map(post => (
<li key={post.id}>
{post.name}
</li>
))
}
</ol>
<ul>
<p>ODDS</p>
{
bet.map(post => (
<li key={post.id}>
{post.odds[4].oddsDecimal}
<div className="stake-margin">
<Stake
allStakes={this.props.valueNewStake}
onChange={() => { this.setState({ allStakes: [...this.props.valueNewStake] }) }}
>
<button>ok</button>
</Stake>
</div>
</li>
))
}
</ul>
</div>
import React, { useState } from 'react';
import CurrencyInput from 'react-currency-input-field';
function Stake() {
const [newStake, setStake] = useState(['']);
const changeStake = (e) => {
setStake(e.target.value)
}
return (
<>
<CurrencyInput
onChange={changeStake}
valueNewStake={newStake}
style={{
marginLeft: "40px",
width: "50px"
}}
placeholder="Stake"
decimalScale={2}
prefix="£"
/>
{newStake}
</>
);
}
export default Stake;
You're not passing your props to your Stake component
function Stake({ allStakes, onChange }) {
// do something with your props here
const [newStake, setStake] = useState(['']);
const changeStake = (e) => {
onChange()
setStake(e.target.value)
}
return (
<>
<CurrencyInput
onChange={changeStake}
valueNewStake={newStake}
style={{
marginLeft: "40px",
width: "50px"
}}
placeholder="Stake"
decimalScale={2}
prefix="£"
/>
{newStake}
</>
);
}

React does not update values nor state

No matter what i write, even if i write a variable and push into it and console.log that value inside filterColumns function it still does not give an updated value nor for the simple variable nor for the state,
console.log(filteringItems) inside filterColumns gives just property value but inside addFilteringItem function it just logs initial state value, can't even think of what is wrong with the code, if you have any additional questions i'll answer any of them.
import { GridColumnsConfig } from './GridColumnsConfig/GridColumnsConfig';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import React, { useEffect, useState } from "react";
import { IOptionsData } from "./Grid";
interface IGridDropdownItemProps {
options: IOptionsData;
filterRequest: any;
cancelFilter?: any;
// filterRequest: (data: IDataRecords) => () => () => void; #comehere
}
function GridDropdownItem(props: IGridDropdownItemProps) {
let id: number = Math.ceil(Math.random() * 1000000000000);
const [filteringItems, setFilteringItems] = useState([...props.options.columns]);
const addFilteringItem = (name:string) => {
let temp = filteringItems.map((item) =>{
if(item.name === name){
item.visible = !item.visible;
}
return item;
});
setFilteringItems([...temp]);
};
const filterColumns = () => {
let columns = [...filteringItems];
// add all column name by default
let data: {
columns: any[];
limit: number;
page: number;
} = {
columns: columns,
limit: props.options.limit,
page: props.options.page,
};
let tempColumns = [
...columns.filter(
(col) =>
Object.entries(columns).filter(
(fi) => fi[0] === col.name && fi[1] === false
).length > 0
),
];
columns.map((col) => {
col.visible = true;
delete col.filter.value;
});
// tempColumns changes part of columns(bcs of pointer) that changes data.columns(bcs of pointer too)
tempColumns.map((col) => {id
col.visible = false;
col.filter.value = null;
});
// console.log(columns, tempColumns);
props.filterRequest(data);
};
return (
<div className="single-button-dropdown uib-dropdown-menu nt-scroll custom-dropdown btn-block old-dropdown-styles ">
<ul
className="dropdown-block scroll vertical hard"
style={{ width: "100%" }}
>
<li>
<DndProvider backend={HTML5Backend}>
<GridColumnsConfig columns={filteringItems} handleCheckboxClick={addFilteringItem}/>
</DndProvider>
</li>
</ul>
<div style={{ position: "absolute", zIndex: 1 }} className="buttons">
<div className="actions">
<div className="pull-right">
<button
type="button"
className="btn btn-primary"
onClick={() => {
filterColumns();
props.cancelFilter();
}}
>
Submit
</button>
{props.options.compactMenu !== undefined &&
props.options.compactMenu ? (
<button
type="button"
className="btn btn-primary"
style={{ background: "#ff8518", color: "#fff" }}
ng-click="saveGridState()"
>
Save
</button>
) : (
<button
className="nd btn btn-default"
onClick={() => props.cancelFilter()}
>
Close
</button>
)}
</div>
</div>
</div>
</div>
);
}
export default GridDropdownItem;
Getting tired really blurred my vision, problem is in mutation.
I'm directly mutating props and that was setting my state to prop value again and again.
let temp = filteringItems.map((item) =>{
if(item.name === name){
item.visible = !item.visible;
}
return item;
});
Fixed Code
let temp = props.options.columns.map((item) =>{
if(item.name === name){
return {...item,visible:!item.visible}
}
return item;
});

Connected component doesn't re-render after store changed from another connected component

I am having a problem related to redux.
I have 2 connected components which are:
avatar situated in the navbar which is always visible
profile which is responsible for changing the avatar image in the store
if I am right, when the store change, any connected component will re-render if needed.
In my case, when the action UPDATE_CURRENT_USER update the avatar image, the navbar avatar doesn't get the new image only after I change route or reload page.
I found a solution but many people say it's a hack,
I have put a listener on store changes in the main component and did forceUpdate()
componentDidMount() {
store.subscribe(res => this.forceUpdate());
}
and I don't want to use it since connected components are supposed to re-render on store changes.
user actions:
export const getCurrentUser = () => dispatch => {
axios.get("user").then(user => {
dispatch({
type: GET_CURRENT_USER,
payload: user.data
});
});
};
export const updateCurrentUser = user => dispatch => {
dispatch({
type: UPDATE_CURRENT_USER,
payload: user
})
}
user reducer
const initialState = {
user: {}
}
export default function (state = initialState, action) {
switch (action.type) {
case GET_CURRENT_USER:
return { ...state, user: action.payload };
case UPDATE_CURRENT_USER:
return { ...state, user: action.payload }
default:
return state;
}
}
profile component
class Profile extends Component {
render() {
const { currentUser, updateCurrentUser } = this.props;
return (
<div id="profile-container">
<ProfileSider
currentUser={currentUser}
updateCurrentUser={updateCurrentUser}
/>
<ProfileContent
currentUser={currentUser}
updateCurrentUser={updateCurrentUser}
/>
</div>
);
}
}
const mapStateToProps = state => ({
currentUser: state.userReducer.user
});
export default connect(
mapStateToProps,
{ updateCurrentUser }
)(Profile);
profile sidebar child of profile
class ProfileSider extends Component {
state = { uploading: false };
triggerAvatarInput() {
$("#avatarInput").click();
}
handleChange = async event => {
this.setState({ ...this.state, uploading: true });
const avatarFormData = new FormData();
avatarFormData.append("file", event.target.files[0]);
axios
.post("uploadFile", avatarFormData)
.then(res => {
const avatarURIFormData = new FormData();
avatarURIFormData.append("avatar", res.data.fileDownloadUri);
axios
.put("user/update", avatarURIFormData)
.then(res => {
const { currentUser } = this.props;
currentUser.avatar = res.data.avatar;
this.props.updateCurrentUser(currentUser);
this.setState({
...this.state,
uploading: false,
avatar: currentUser.avatar
});
message.success("Avatar updated successfully", 3);
})
.catch(error => {
this.setState({ ...this.state, uploading: false });
message.error("Updating avatar failed!", 3);
});
})
.catch(error => {
this.setState({ ...this.state, uploading: false });
message.error("Uploading avatar failed!", 3);
});
};
render() {
const { uploading } = this.state;
const { currentUser } = this.props;
return (
<div id="profile-sider">
<div id="profile-sider-info">
<div id="profile-sider-info-avatar">
<div className="container">
<div
className="overlay-uploading"
className={
uploading ? "overlay-uploading" : "overlay-uploading hidden"
}
>
<Icon type="loading" style={{ fontSize: 50, color: "#FFF" }} />
</div>
<div className="overlay" />
<div className="overlay-text" onClick={this.triggerAvatarInput}>
<Icon type="camera" style={{ fontSize: 20 }} />
<span>Update</span>
</div>
<div
className="avatar"
style={{
backgroundImage: "url(" + currentUser.avatar + ")"
}}
></div>
<input
onChange={this.handleChange}
type="file"
accept="image/png, image/jpeg, image/jpg"
id="avatarInput"
/>
</div>
</div>
<h2 style={{ marginTop: 20, textAlign: "center" }}>
{currentUser.fullName}
</h2>
<h4 style={{ textAlign: "center" }}>{currentUser.email}</h4>
</div>
<div id="profile-sider-actions">
<div className="profile-sider-actions-item">
<Link to="/profile/courses" style={{ transition: 0 }}>
<Button type="primary" id="courses-btn">
<Icon type="read" style={{ marginRight: 15 }} />
My Courses
</Button>
</Link>
</div>
<div className="profile-sider-actions-item">
<Link to="/profile/update">
<Button type="primary" id="update-infos-btn">
<Icon type="sync" style={{ marginRight: 15 }} />
Update Infos
</Button>
</Link>
</div>
</div>
</div>
);
}
}
export default ProfileSider;
avatar component situated in navbar
class ProfileAvatar extends Component {
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
this.handleOutsideClick = this.handleOutsideClick.bind(this);
this.state = {
showProfileDropdown: false
};
}
componentDidMount() {
this.props.getCurrentUser();
}
handleLogout = async () => {
try {
await auth.logout();
this.props.onLogout();
notification["success"]({
message: "You have been successfully logged out!"
});
} catch (ex) {}
};
handleClick() {
if (!this.state.showProfileDropdown) {
// attach/remove event handler
document.addEventListener("click", this.handleOutsideClick, false);
} else {
document.removeEventListener("click", this.handleOutsideClick, false);
}
this.setState(prevState => ({
showProfileDropdown: !prevState.showProfileDropdown
}));
}
handleOutsideClick(e) {
// ignore clicks on the component itself
if (this.element && this.element.contains(e.target)) {
return;
}
this.handleClick();
}
render() {
const { currentUser } = this.props;
return (
<div
className="profile-avatar"
ref={element => {
this.element = element;
}}
>
<Avatar
onClick={this.handleClick}
size="large"
style={{ color: "#f56a00", backgroundColor: "#fde3cf" }}
src={currentUser.avatar}
>
{currentUser.fullName ? currentUser.fullName.charAt(0) : null}
</Avatar>
{this.state.showProfileDropdown && (
<div className="profile-dropdown-list">
<List
className="dropdown_list dropdown-shadow "
size="small"
style={{ width: "150px" }}
bordered
itemLayout="vertical"
dataSource={[
<Link to="/profile/update" className="profile-list-item">
<List.Item className="list-item">
<Icon className="profile-icons" type="user" /> My Profile
</List.Item>
</Link>,
<Link to="/profile/courses" className="profile-list-item">
<List.Item className="list-item">
<Icon className="profile-icons" type="container" /> My
Courses
</List.Item>
</Link>,
<List.Item className="list-item">
<Icon className="profile-icons" type="question-circle" /> Ask
for Help
</List.Item>,
<List.Item className="list-item" onClick={this.handleLogout}>
<Icon className="profile-icons" type="logout" /> Log out
</List.Item>
]}
renderItem={item => item}
/>
</div>
)}
</div>
);
}
}
const mapStateToProps = state => ({
currentUser: state.userReducer.user
});
export default connect(
mapStateToProps,
{ getCurrentUser }
)(ProfileAvatar);
image: https://imge.to/i/vywTNj
There are two problems here:
You are mutating the existing object from the store
You are sending that exact same user object back into the store when you dispatch the action.
Specifically, these lines are the cause:
const { currentUser } = this.props;
currentUser.avatar = res.data.avatar;
this.props.updateCurrentUser(currentUser);
currentUser is the user object that's already in the Redux store. This code mutates the object, and inserts it back into the store.
That results in the connected component thinking nothing has actually changed.
The shortest way to fix this is to create a new user object, and insert that:
const {currentUser} = this.props;
const updatedUser = {...currentUser, avatar: res.data.avatar};
this.props.updateCurrentUser(updatedUser);
To avoid this in the future, I strongly encourage you to use the configureStore function from our Redux Starter Kit package, which detects mutations and will throw errors if you mutate.

ReactJS - popover onClick insde data-content doesn't work

I'm using ReactJS and I'm trying to do a delete confirm popover and it works but when I click on "delete" doesn't work at all!
I'm using a button and inside I have an Icon and when click should popover its data-content, and it does! but the onClick inside doesn't work, maybe it's the way that I'm writing it? Have no idea what's going on
What I'm doing wrong?
Thank you, hope you can help me!
import React, { Component } from 'react';
import moment from 'moment';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faTrashAlt, faEdit, faTimes } from '#fortawesome/free-solid-svg-icons'
import NewTask from '../NewTask/NewTask';
import './Tasks.css';
class Tasks extends Component {
constructor(props) {
super(props);
this.state = {
projectId: props._id,
project: props.project,
tasks: []
};
}
componentDidMount() {
fetch(`/dashboard/project/${this.props.projectId}/tasks`)
.then(response => {
return response.json()
}).then(task => {
this.setState({
tasks: task.tasks
})
}).catch(error => console.error('Error:', error));
}
appendTask = task => {
let tasks = this.state.tasks;
tasks.push(task);
this.setState({tasks});
}
removeTask = taskId => {
let tasks = this.state.tasks;
let taskToDel = tasks.find(function(element){
if(element._id === taskId) {
return element;
}
});
if(taskToDel) {
// Find index of task to remove
const index = tasks.indexOf(taskToDel);
// If task index is found remove task from array
if(index != -1)
tasks.splice(index, 1);
this.setState({tasks});
}
}
deleteTaskOnDb = task => {
let data = {taskId: task._id};
fetch(`/dashboard/project/${this.props.projectId}/tasks/delete`, {
method: 'delete',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
}
}).then(response => {
//if(response.status === 200)
return response.json()
}).then(taskId => {
this.removeTask(taskId);
}).catch(error => console.error('Error:', error));
}
render() {
//POPOVER FUNCTION HERE
$(function () {
$('[data-toggle="popover"]').popover()
})
//POPOVER FUNCTION HERE
const fontawesomeiconStyle = {
fontSize: '1em',
color: '#bd822a',
textAlign: 'center'
}
const listStyle = {
display:'flex',
flexFlow: 'row wrap',
justifyContent: 'space-between'
}
const { tasks } = this.state;
return (
<div>
<ul className="task-list">
{tasks.map(task =>
<li key={task._id} style={listStyle}> <div>
{task.tasktitle}
<br/>
<p style={{fontSize: '10px', color: '#a2a2a2'}}>{moment(task.created).format('MMM DD YY, h:mm A')} </p>
</div>
<div>
<p style={{fontSize: '12px', color: '#a2a2a2'}}>{task.taskcomment}</p>
</div>
<div>
//BUTTON HERE
<button type="button" className="example-popover btn--delete button--tasks" data-html="true" data-container="body" data-toggle="popover" data-placement="right" data-content="Are you sure? <button type='button' className='btn--delete button--tasks' onClick='{() => this.deleteTaskOnDb(task)}'> Delete</button>">
<FontAwesomeIcon style={fontawesomeiconStyle} icon={faTimes} />
</button>
//BUTTON HERE
</div>
</li>
)}
</ul>
{this.props.project &&
this.props.project._id &&
<NewTask projectId={this.props.project._id} appendTask={this.appendTask}/>
}
</div>
);
}
}
export default Tasks;

Using sweet alert 2 getting this.props is undefined after using Warning alert

I am getting this.props is undefined when using the sweetalert2 warning alert. I am trying to let a user confirm before deleting their profile, but I assume this must be doing something to this.props?
Here is the code. Everything is hooked up to redux correctly as I have other places I am calling this.props and it works just fine, so I will be only placing the function where this is breaking, however if you feel that this post would benefit from having all the code I will make an edit.
the function is called with an onClick event from a button. I have console.log and the button is fully working and calls the function:
<button
className="btn btn-danger btn-lg btn-block"
onClick={this.deleteProfile.bind(this)}
>
DELETE Profile
</button>
To clarify the error message I will add a picture of the console:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import swal from 'sweetalert2/dist/sweetalert2.all.min.js';
import actions from '../../actions';
import { UpdateProfile } from '../view';
import { DateUtils } from '../../utils';
class Profile extends Component {
constructor() {
super();
this.state = {
profile: {
image:
'https://lh3.googleusercontent.com/EJf2u6azJe-TA6YeMWpDtMHAG6u3i1S1DhbiUXViaF5Pyg_CPEOCOEquKbX3U-drH29oYe98xKJiWqYP1ZxPGUQ545k',
bannerImage:
'https://lh3.googleusercontent.com/RAdfZt76XmM5p_rXwVsfQ3J8ca9aQUgONQaXSE1cC0bR0xETrKAoX8OEOzID-ro_3vFfgO8ZMQIqmjTiaCvuK4GtzI8',
firstName: 'First Name',
lastName: 'Last Name',
email: 'Contact Email',
bio: 'Bio will go here'
}
};
}
componentDidMount() {
const { id } = this.props.match.params;
if (this.props.profiles[id] != null) {
return;
}
this.props
.getProfile(id)
.then(() => {})
.catch(err => {
console.log(err);
});
}
createUpdatedProfile(params) {
const { id } = this.props.match.params;
const profile = this.props.profiles[id];
const { currentUser } = this.props.user;
if (currentUser.id !== profile.id) {
swal({
title: 'Oops...',
text: 'You do not own this profile',
type: 'error'
});
return;
}
this.props
.updateProfile(currentUser, params)
.then(response => {
swal({
title: `${response.username} Updated!`,
text: 'Thank you for updating your profile',
type: 'success'
});
})
.catch(err => {
console.log(err);
});
}
deleteProfile() {
const { id } = this.props.match.params;
const profile = this.props.profiles[id];
const { currentUser } = this.props.user;
if (currentUser.id !== profile.id) {
swal({
title: 'Oops...',
text: 'You do not own this profile',
type: 'error'
});
return;
}
swal({
title: 'Are you sure?',
text: 'Your Profile will be lost forever!',
type: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Yes, delete it!'
}).then(() => {
this.props
.deleteProfile(profile)
.then(() => {
this.props.history.push('/');
swal('Deleted!', 'Your Profile has been deleted.', 'success');
})
.catch(err => {
console.log(err);
});
});
}
render() {
const { id } = this.props.match.params;
const profile = this.props.profiles[id];
const { currentUser } = this.props.user;
const defaultProfile = this.state.profile;
const bannerUrl =
profile == null
? defaultProfile.bannerImage
: profile.bannerImage || defaultProfile.bannerImage;
const bannerStyle = {
backgroundImage: `url(${bannerUrl})`,
backgroundSize: '100%',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center'
};
const nameStyle = {
background: 'rgba(255, 255, 255, 0.7)',
borderRadius: '8px'
};
const imageStyle = {
maxHeight: '150px',
margin: '20px auto'
};
return (
<div>
{profile == null ? (
<div>
<h1>Profile no longer exists</h1>
</div>
) : (
<div>
<div className="jumbotron jumbotron-fluid" style={bannerStyle}>
<div className="container" style={nameStyle}>
<img
src={profile.image || defaultProfile.image}
style={imageStyle}
className="rounded img-fluid mx-auto d-block"
/>
</div>
</div>
<div className="row">
<div className="col-sm-12">
<h1 className="display-3 text-center">{profile.username}</h1>
<p className="lead text-center">
{profile.firstName || defaultProfile.firstName}{' '}
{profile.lastName || defaultProfile.lastName}
</p>
<p className="lead text-center text-muted">
{profile.email || defaultProfile.email}
</p>
<p className="text-center text-muted">
User since: {DateUtils.relativeTime(profile.timestamp)}
</p>
<hr className="my-4" />
<p className="lead" style={{ border: '1px solid #e6e6e6', padding: '20px' }}>
{profile.bio || defaultProfile.bio}
</p>
</div>
</div>
{currentUser == null ? null : currentUser.id !== profile.id ? null : (
<div>
<UpdateProfile
currentProfile={profile}
onCreate={this.createUpdatedProfile.bind(this)}
/>
<div className="row justify-content-center" style={{ marginBottom: '100px' }}>
<div className="col-sm-6">
<button
className="btn btn-danger btn-lg btn-block"
onClick={this.deleteProfile.bind(this)}
>
DELETE Profile
</button>
</div>
</div>
</div>
)}
</div>
)}
</div>
);
}
}
const stateToProps = state => {
return {
profiles: state.profile,
user: state.user
};
};
const dispatchToProps = dispatch => {
return {
getProfile: id => dispatch(actions.getProfile(id)),
updateProfile: (currentUser, params) => dispatch(actions.updateProfile(currentUser, params)),
deleteProfile: entity => dispatch(actions.deleteProfile(entity))
};
};
export default connect(stateToProps, dispatchToProps)(Profile);
Try to bind your deleteProfile function to the class in the constructor using
this.deleteProfile = this.deleteProfile.bind(this);
Or you can change the definition of the function and use an arrow function to define it.
deleteProfile=()=>{
... //rest of function body
}
and remove the bind from the onClick handler

Resources