Passing state between nested components - reactjs

I am trying to open up a Modal component on a onClick from a listItem in a listGroup component. However, the setup I currently have either causes my application to hang and I am not able to click anything on the application or the state does not get updated and the modal does not render.
Another weird that thing that occurs is when I console log to see what the showModalState is, the state changes but when I check the react developer tools to see if it changed, it's always at the initial state which is false.
The error more than likely comes from the ModalActions.ts or ModalReducer.ts.
Note: All the code provided below are just snippets. I omitted alot of stuff and left only what I thought could be the issue.
This is my ModalTypes.ts
export const SHOW_MODAL = "SHOW_MODAL";
interface ShowModal {
type: typeof SHOW_MODAL;
payload: boolean;
}
export type ModalActionTypes = ShowModal;
This is my ModalActions.ts
import { SHOW_MODAL, ModalActionTypes } from "./ModalTypes";
export function UpdateModal(modal: boolean): ModalActionTypes {
return {
type: SHOW_MODAL,
payload: modal
};
}
This is my IModalState.ts
export interface IModalState {
showModal: boolean;
}
This is my ModalReducer.ts. **I will probably make actions and types to hide the modal as well
import { ModalActionTypes, SHOW_MODAL } from "../actions/ModalTypes";
import { IModalState } from "../models/IModalState";
const initialState: IModalState = {
showModal: false
};
export function modalReducer(state = initialState, action: ModalActionTypes) {
switch (action.type) {
case SHOW_MODAL:
return {
...state,
showModal: action.payload
};
default:
return state;
}
}
This is my App.tsx
<ListGroup
onUpdateModal={this.props.onUpdateModal}
showModalState={this.props.showModalState}
/>
const mapStateToProps = (state: AppState) => ({
showModalState: state.modal
});
const mapDispatchToProps = (dispatch: any) => {
return {
onUpdateModal: bindActionCreators(UpdateModal, dispatch)
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
This is my ListGroup.tsx
import { UpdateModal } from "../actions/ModalActions";
import { IModalState } from "../models/IModalState";
interface IProps {
onUpdateModal: typeof UpdateModal;
showModalState: IModalState;
}
// interface IState {
// showModal: boolean;
// }
export class ListGroup extends React.Component<IProps> {
// IState
// public state: IState = {
// showModal: false
// };
// showModal = () => {
// // Show the modal
// this.setState({ showModal: true });
// };
public render() {
// const { showModal } = this.state;
return (
<div>
<ul
className="list-group"
style={{
marginTop: "20px",
display: "inline-block"
}}
>
{filterTests.map(filterTest => (
<li
className="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
onClick={() => {
this.props.onUpdateModal(true);
console.log(this.props.onUpdateModal(true));
console.log(this.props.showModalState);
this.props.onUpdateSelectedTest(filterTest);
// this.showModal();
}}
>
{filterTest.companyPN}: {filterTest.description}
</li>
))}
</ul>
{/* Show the modal if showModal is true */}
{this.props.showModalState && (
<TestGroup
testState={this.props.testState}
onUpdateSelectedWedge={this.props.onUpdateSelectedWedge}
/>
)}
</div>
);
}
}
This my TestGroup.tsx
interface IProps {
onUpdateModal: typeof UpdateModal;
showModalState: IModalState;
}
export class TestGroup extends React.Component<IProps> {
// hideModal = () => {
// this.setState({
// showModal: !this.props.showModal
// });
// };
public render() {
return (
<div>
<div className="modal" style={{ display: "inline-block" }}>
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title"></h5>
<button
type="button"
className="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
</div>
<div className="modal-footer">
<button
// onClick={() => {
// this.hideModal();
// }}
type="button"
className="btn btn-secondary"
data-dismiss="modal"
>
Close
</button>
</div>
</div>
))}
</div>
</div>
</div>
);
}
}
export default TestGroup;

I'd rather leave this as a comment, but I don't have the privilege at the moment.
In your app.ts mapStateToProps function, I think you want showModalState to be
showModalState: state.modal.showModal

In Apps.tsx instead of this.props.showModalState it should have been this.props.showModalState.showModal
{this.props.showModalState.showModal && (
<TestGroup
testState={this.props.testState}
onUpdateSelectedWedge={this.props.onUpdateSelectedWedge}
/>
)}

Related

Github search not populating the page with profiles

I am new to react and following a tutorial where the instructor connects to the github api in order to populate the page with user profiles based upon your search. However I am not able to do the same.
We need to connect to the github api and use the search/users query to get users with the help of axios.
This is my code in app.js:
const github = axios.create({
baseURL: "https://api.github.com",
timeout: 1000,
headers: { Authorization: process.env.REACT_APP_GITHUB_TOKEN },
});
class App extends Component {
state = {
users: [],
loading: false,
};
static propTypes = {
searchUsers: PropTypes.func.isRequired,
};
searchUsers = async (text) => {
this.setState({ loading: true });
const res = await axios.get(
`https://api.github.com/search/users?q=${text}`,
{
headers: {
Authorization: `${process.env.REACT_APP_GITHUB_TOKEN}`,
},
}
);
this.setState({ users: res.data.items, loading: false });
};
render() {
return (
<div className="App">
<Navbar />
<div className="conatiner">
<Search searchUsers={this.searchUsers} />
<Users loading={this.state.loading} users={this.state.users} />
</div>
</div>
);
}
}
export default App;
You need to create a personal token with github in order to use the github api.
This is my code in Search component:
export class Search extends Component {
state = {
text: "",
};
onSubmit = (e) => {
e.preventDefault();
this.props.searchUsers(this.state.text);
this.setState({text:""});
};
onChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
render() {
return (
<div>
<form onSubmit={this.onSubmit} className="form">
<input
type="text"
name="text"
placeholder="Search Users...."
value={this.state.text}
onChange={this.onChange}
/>
<input
type="submit"
value="Search"
className="btn btn-dark btn-block"
/>
</form>
</div>
);
}
}
export default Search;
However, I am not able to search for profiles on the page. Could anybody help me please.
This is what I added in .env.local in the root file of my app
REACT_APP_GITHUB_TOKEN='token <...>'
Here is Users.js:
const Users = ({ users, loading }) => {
if (loading) {
return <Spinner />;
} else {
return (
<div style={userStyle}>
{users.map((user) => (
<UserItem key={user.id} user={user} />
))}
</div>
);
}
};
Users.propTypes = {
users: PropTypes.array.isRequired,
loading: PropTypes.bool.isRequired,
};
const userStyle = {
display: "grid",
gridTemplateColumns: "repeat(3,1fr)",
gridGap: "1rem",
};
export default Users;
Here is UserItem.js:
const UserItem = ({ user: { login, avatar_url, html_url } }) => {
return (
<div className="card text-center">
<img
src={avatar_url}
alt=""
className="round-img"
style={{ width: "60px" }}
/>
<h3>{login}</h3>
<div>
<a href={html_url} className="btn btn-dark btn-sm my-1">
More
</a>
</div>
</div>
);
};
UserItem.propTypes = {
user: PropTypes.object.isRequired,
};
export default UserItem;

React how to make children components have their own state

class States extends Component {
componentDidMount() {
this.props.getForSaleStates()
}
render() {
return (
<div>
{this.props.post.states.map((eachState, index) => (
<State key={index} eachState={eachState} />
))}
</div>
)
}
}
const mapStateToProps = state => ({
post: state.post,
auth: state.form
})
export default connect(mapStateToProps, {
getForSaleStates
})(States)
class State extends Component {
state = {
isOpen: false,
cities: []
}
componentDidUpdate(prevProps) {
if (this.props.post.cities !== prevProps.post.cities) {
this.setState({
cities: this.props.post.cities
})
}
}
handleClick = (e) => {
if (!this.state.isOpen) {
this.props.getForSaleTowns(this.props.eachState)
}
this.setState({
isOpen: !this.state.isOpen
})
}
render() {
console.log(this.state)
return (
<div>
<ListGroupItem
style={{ fontSize: '12px' }} className='p-0 border-0'
tag="a" href="#"
onClick={this.handleClick}>
{!this.state.isOpen ? (
<i className="far fa-plus-square mr-1" style={{ fontSize: '1em' }}>
</i>
) : (
<i className="far fa-minus-square mr-1" style={{ fontSize: '1em' }}>
</i>
)}
{this.props.eachState}
</ListGroupItem>
<Collapse isOpen={this.state.isOpen}>
<Cities cities={this.state.cities} />
</Collapse>
</div>
)
}
}
const mapStateToProps = state => ({
post: state.post,
auth: state.form
})
export default connect(mapStateToProps, { getForSaleTowns })(State)
class Cities extends Component {
render() {
// console.log(this.props)
return (
<div>
<ListGroup >
{this.props.cities.map((city, index) => (
<ListGroupItem key={index} style={{ fontSize: '11px' }} className='border-0' tag="a" href="#">
{city}
</ListGroupItem>
))}
</ListGroup>
</div>
)
}
}
const mapStateToProps = state => ({
post: state.post,
auth: state.form
})
export default connect(mapStateToProps)(Cities)
Explanation: States => State => Cities
Getting all the states from database, mapping them out to have each other their own state, then calling towns in that state by sending another action to redux and getting towns in that specific state.
the above code works fine, only thing is when i click on one state collapse opens up and show towns from that state. however if i click on another state, towns from redux changes and therefore the list above changes.
here is a photo that explains what I mean:
How Can i make it so every state has its own cities listed instead of they all becoming the same
componentDidUpdate(prevProps) {
if (this.props.post[state_id].cities !== prevProps.post[state_id].cities) {
this.setState({
cities: this.props.post.cities
})
}
}
post Reducer
export const postReducer = (state, action) => {
[...Old code]
case [TOWN_FETCH_SUCCESS]:
return {
...state,
// your unique identifier for each state
[state_id]: action.payload
}
}
While saving you can save towns inside that id of the state

React modal always take the last element of map function

I am currently creating a React todo application. So basically I have two component TodoList and TodoItem
TodoList component will receive an array of objects consisting title as the property and map through this with TodoItem component
In my TodoItem component, the user can choose to edit or delete the item. If the user chose to edit, a modal will show up with the existing title using a textarea. However, I have trouble implementing this function as the modal will always show up with the last element of the array.
TodoList Component
import React, { Component } from 'react'
import TodoItem from './TodoItem'
import { connect } from 'react-redux'
import { clear_todo } from '../store/actions/todoActions'
class Todolist extends Component {
clearList = (e) => {
e.preventDefault()
this.props.clearList(clear_todo());
}
handleChange = (index, title) => {
this.setState({
[index]: title
})
}
render() {
const { items, editItem } = this.props.todo
return (
<ul className="list-group my-5">
<h3 className="text-capitalize text-center">
Todo List
</h3>
{
items.map((item, index) => {
const editedTitle = item.title
return (<TodoItem key={item.id} title={item.title}
id={item.id}
editedTitle={editedTitle}
onChange={this.handleChange}
editItem={editItem} />)
})
}
<button className="btn btn-danger btn-block text-capitalize mt-5" onClick={this.clearList}> Clear List </button>
</ul>
)
}
}
const mapStateToProps = state => {
return {
todo: state.todo
}
}
const mapDispatchToProps = dispatch => {
return {
clearList: (clear_todo) => { dispatch(clear_todo) }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Todolist)
TodoItem Component
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { delete_todo, edit_todo, toggle_edit } from '../store/actions/todoActions'
import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Form, FormGroup, Input } from 'reactstrap';
import TodoEditItem from './TodoEditItem'
class Todoitem extends Component {
// constructor(props) {
// super(props)
// this.state = {
// [props.id]: props.title
// }
// }
handleEdit = (id, title) => {
this.props.editTodo(edit_todo(id, title))
}
toggleEdit = (editItem, title) => {
this.props.toggleEdit(toggle_edit(!editItem))
// this.initializeTitle(title)
}
handleDelete = (id) => {
this.props.deleteTodo(delete_todo(id))
}
// onChange = (e, id) => {
// this.setState({
// [id]: e.target.value
// })
// }
componentDidMount() {
// console.log(this.props)
// this.initializeTitle(this.props.title)
this.setState({
[this.props.id]: this.props.editedTitle
})
console.log(this.state)
}
render() {
// console.log(this.state)
let { id, title, editItem, editedTitle, index } = this.props
console.log(id)
// console.log(index)
// let { item } = this.state
return (
<div>
<li className="list-group-item text-capitlize d-flex justify-content-between my-2">
<h6>{title}</h6>
<div className="todo-icon">
<span className="mx-2 text-success" onClick={this.toggleEdit.bind(this, editItem)} >
<i className="fas fa-pen"></i>
</span>
<span className="mx-2 text-danger" onClick={this.handleDelete.bind(this, id)}>
<i className="fas fa-trash"></i>
</span>
</div>
<Modal isOpen={editItem}>
<ModalHeader>Edit Todo Item</ModalHeader>
<ModalBody>
<Form>
<FormGroup row>
<Input type="textarea" name="text" value={this.state ? this.state[id] : ""} onChange={this.props.onChange} />
</FormGroup>
</Form>
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={this.handleEdit.bind(this, id, editedTitle)}>Save</Button>{' '}
<Button color="secondary" onClick={this.toggleEdit.bind(this, editItem)}> Cancel</Button>
</ModalFooter>
</Modal>
</li>
{/* {editItem ? <TodoEditItem title={title} editItem={editItem} /> : ''} */}
</div>
)
}
}
const mapDispatchToProps = dispatch => {
return {
deleteTodo: (delete_todo) => { dispatch(delete_todo) },
editTodo: (edit_todo) => { dispatch(edit_todo) },
toggleEdit: (toggle_edit) => { dispatch(toggle_edit) },
}
}
export default connect(null, mapDispatchToProps)(Todoitem)
Sample Image
Image of TodoItem
Image of Modal
As you can see from the image of TodoItem, I am trying to edit "Take out the garbage" but my textarea has been prepopulated as the last element.
You're using the same variable to determine all of the TodoItem's open state. The result is it appears that it is only taking the last value from the array, but really each modal is opening at once and the last one is the only visible modal.
// editItem here is used to determine the open state of both modals
const { items, editItem } = this.props.todo
...
{
items.map((item, index) => {
const editedTitle = item.title
return (
<TodoItem
key={item.id}
title={item.title}
id={item.id}
editedTitle={editedTitle}
onChange={this.handleChange}
editItem={editItem} // Same value
/>
)
})
}
...
let { editItem } = this.props
<Modal isOpen={editItem}> // All will open and close at the same time
Instead, use a different flag, or just manage the open state in each child like this:
<span className="mx-2 text-success" onClick={() => this.setState({open: true})} >
<i className="fas fa-pen"></i>
</span>
...
<Modal isOpen={this.state.open}>

react redux containers send state to another container

Hello I have an action to get data from my backend and then I created a new container and I use this state and I created an action to filter this state, and then when it clicks back to my component product the products that were filtered
My action fetch:
export const fetchProduct = () => {
console.log('action')
return dispatch => {
dispatch(fetchStarted());
api
.get('/products')
.then(res => {
dispatch(fetchSucess(res.data));
})
.catch(err => {
dispatch(fetchFailed(err.message));
});
};
};
My reducer fetch:
const initialState = {
loading: false,
products: [],
error: null
};
export default function productReducer(state = initialState, action) {
switch (action.type) {
case FETCH_LOADING:
return {
...state,
loading: true
};
case FETCH_SUCESS:
return {
...state,
loading: false,
error: null,
...state, products: action.data
};
case FETCH_FAIL:
return {
...state,
loading: false,
error: action.error
};
default:
return state;
}
}
My container product:
class HomeProducts extends Component {
componentDidMount() {
this.props.fetchProduct();
}
render() {
const productItems = this.props.products.map( product =>(
<div className="col-md-4 pt-4 pl-2" key={product.id}>
<div className = "thumbnail text-center">
<a href={`#${product.id}`} onClick={(e)=>this.props.handleAddToCard(e,product)}>
<p>
{product.name}
</p>
</a>
</div>
<b>{util.formatCurrency(product.price)}</b>
<button className="btn btn-primary" onClick={(e)=>this.props.handleAddToCard(e,product)}>Add to Cart</button>
</div>
)
)
return (
<div className="container">
<div className="row">
{productItems}
</div>
</div>
)
}
}
const mapStateToProps = (state) => {
return{
products: state.data.products,
loading: state.data.loading,
error: state.data.error
}
};
const mapActionsToProps = {
fetchProduct: fetchProduct
};
export default connect(mapStateToProps, mapActionsToProps)(HomeProducts);
My product container will show in a div all the products it fetched in the backend.
And now My container filter:
class FilterHome extends Component {
render() {
console.log(this.props);
return (
<>
<div className="row">
<button className="filterbt btn btn-danger btn-rounded">Filters</button>
<div className=" mt-4 d-flex flex-column">
<p className="textCategory">CATEGORY</p>
<div className="category d-flex flex-column">
<p>Stat Trak</p>
<p>Normal</p>
</div>
<p className="textCategory">EXTERIOR</p>
<div className="category d-flex flex-column">
<p value={2} onChange={(e) => this.props.filterProducts(this.props.products, e.target.value)}>Factory New ()</p>
<p>Minimal Wear ()</p>
<p>Field Tested ()</p>
<p>Well Worn ()</p>
<p>Battle Scarred ()</p>
</div>
</div>
</div>
</>
)
}
}
const mapStateToProps = state => ({
products: state.data.products,
});
export default connect(mapStateToProps, {filterProducts})(FilterHome);
and then I created an action and a redux to get the filtered product list and then send this filtered stripe to my div that displays the products.
Action filter products:
import {FILTER_PRODUCT} from '../constants/fetchTypes';
export const filterProducts = (products,value) => (dispatch) => {
return dispatch({
type:FILTER_PRODUCT,
payload:{
value:value,
products:value === ''? products: products.filter(v => v.id_sub = value)
}
})
}
My reducer filter:
import {
FETCH_SUCESS,
FETCH_FAIL,
FETCH_LOADING,
FILTER_PRODUCT
} from '../constants/fetchTypes';
const initialState = {
products: []
};
export default function productReducer(state = initialState, action) {
switch (action.type) {
case FILTER_PRODUCT:
return {
...state,
filteredProducts: action.payload.products
};
default:
return state;
}
} return state;
}
}
I don't know how I can change the filtered products in my product div

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.

Resources