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
Related
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}
/>
)}
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.
Just learning React, and I would like to add an onClick on the font awesome icon, and run the markTaskAsCompleted function. I'm having trouble because it's several components lower in the hierarchy. How would you ideally go about this? Bear in mind that I also have to pass the ID of the task in the function.
class TasksBase extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false,
tasks: [],
};
}
componentDidMount() {
this.onListenForTasks();
}
onListenForTasks() {
this.setState({ loading: true });
this.unsubscribe = this.props.firebase
.tasks()
.orderBy('created', 'desc')
.onSnapshot(snapshot => {
if (snapshot.size) {
let tasks = [];
snapshot.forEach(doc =>
tasks.push({ ...doc.data(), uid: doc.id }),
);
this.setState({
tasks: tasks,
loading: false
});
} else {
this.setState({ tasks: null, loading: false });
}
});
}
markTaskAsCompleted(){
console.log("Completed");
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
const { tasks, loading } = this.state;
return (
<div>
{loading && <div>Loading ...</div>}
{tasks ? (
<TaskList tasks={tasks} />
):(
<div>There are no tasks ...</div>
)}
</div>
);
}
}
const Tasks = withFirebase(TasksBase);
const TaskList = ({ tasks }) => (
<ul className="tasks">
{tasks.map( task => (
<Task key={task.uid} task={task} />
))}
</ul>
);
const Task = ({ task }) => (
(!task.completed && !task.obsolete && !task.waitingForDependencies) &&
<li className="task">
<strong>{task.userId}</strong> {task.name}
<div className="icons">
<FontAwesomeIcon icon="check-circle"/>
<FontAwesomeIcon icon="times-circle" />
</div>
</li>
);
const condition = authUser => !!authUser;
export default compose(
withEmailVerification,
withAuthorization(condition),
)(Tasks);
Bind your class function in the constructor:
this.markTaskAsCompleted = this.markTaskAsCompleted.bind(this);
Pass the function into the child component with props:
<TaskList tasks={tasks} handleMarkCompleted={this.markTaskAsCompleted} />
Pass the function again to child component, this is prop drilling and is not the latest greatest approach but it works:
const TaskList = ({ tasks, handleMarkCompleted }) => (
<ul className="tasks">
{tasks.map( task => (
<Task key={task.uid} task={task} handleMarkCompleted={handleMarkCompleted} />
))}
</ul>
);
Trigger the function with onClick:
inside <Task>...
<FontAwesomeIcon icon="check-circle" onClick={() => handleMarkCompleted(task.uid)} />
If passing data into the function (ex. task.uid) make it a param in the function definition as well so you can use it:
markTaskAsCompleted(id){
console.log("Completed", id);
}
you will need to pass it down the tree as
markTaskAsCompleted={this.props.markTaskAsCompleted}
and make sure that the function is bound to the parent in the constructor.
u can use refs or document.getElementById to get the ID.
Help! My child component is not updating in my react app!
I want to bring cartNumber to the page component which then is passed onto header component but the number doesn't even show up!
Parent component
class Shop extends Component {
constructor(props) {
super(props);
this.state = {
merchants: [],
error: null,
loading: true,
order: []
};
}
componentWillMount() {
Meteor.call("merchants.getMerchants", (error, response) => {
if (error) {
this.setState(() => ({ error: error }));
} else {
this.setState(() => ({ merchants: response }));
}
});
}
componentDidMount() {
setTimeout(() => this.setState({ loading: false }), 800); // simulates loading of data
}
goBack = () => this.props.history.push("/");
goCart = () => {
try {
Orders.insert(this.state.order), this.props.history.push("/cart");
} catch (error) {
throw new Meteor.Error("there was an error", error);
}
};
onAddToCart(cartItem) {
let { order } = this.state;
order.push(cartItem);
console.log(order.length);
}
render() {
const { loading } = this.state;
const { merchants, error } = this.state;
const { data } = this.state;
const { order } = this.state;
const getProductsFromMerchant = ({ products, brands }) =>
products.map(({ belongsToBrand, ...product }) => ({
...product,
brand: brands[belongsToBrand]
}));
const products = merchants.reduce(
(acc, merchant) => [...acc, ...getProductsFromMerchant(merchant)],
[]
);
if (loading) {
return (
<Page
pageTitle="Shop"
history
goBack={this.goBack}
goCart={this.goCart}
>
<div className="loading-page">
<i
className="fa fa-spinner fa-spin fa-3x fa-fw"
aria-hidden="true"
/>
<br /> <br />
<span>Loading...</span>
</div>
</Page>
);
}
return (
<Page
pageTitle="Shop"
history
goBack={this.goBack}
goCart={this.goCart}
cartNumber={order.length}
>
<div className="shop-page">
{products.map(({ id, ...product }) =>
<Product
{...product}
key={id}
history
onAddToCart={this.onAddToCart.bind(this)}
/>
)}
</div>
</Page>
);
}
}
export default Shop;
Here is the page component which contains the header component
export const Page = ({
children,
pageTitle,
history,
goBack,
goCart,
cartNumber
}) =>
<div className="page">
<Header goBack={goBack} goCart={goCart} history cartNumber>
{pageTitle}
</Header>
<main>
<MuiThemeProvider>
{children}
</MuiThemeProvider>
</main>
<Footer />
</div>;
export default Page;
And Finally this is the header where I want to bring the cartNumber into.
const Header = ({ children, goBack, goCart, cartNumber, pageTitle }) =>
<header>
<button onClick={goBack} className="back-button">
{/* Image added here to show image inclusion, prefer inline-SVG. */}
<img alt="Back" src={`/icon/header/back-white.svg`} />
</button>
<h1>
{children}
</h1>
<div className="right-content">
( {cartNumber} )
<i
className="fa fa-shopping-cart fa-2x"
aria-hidden="true"
onClick={goCart}
/>
</div>
</header>;
export default withRouter(Header);
You're passing cartNumber as a boolean:
<Header goBack={goBack} goCart={goCart} history cartNumber>
Pass it as a value:
<Header goBack={goBack} goCart={goCart} history={history} cartNumber={cartNumber}>
I am having some trouble dealing with an async issue. The render is happening before the state is set at getStepContent(0) causing me to lose access to the state's values when I pass it down a component (CartInfo). Any ideas?
class Cart extends PureComponent {
constructor(props) {
super(props);
this.state = {
order: [],
error: null,
finished: false,
stepIndex: 0
};
}
componentWillMount() {
Meteor.call("orders.getLastOrder", (error, response) => {
if (error) {
this.setState(() => ({ error: error }));
console.log(error);
} else {
this.setState(() => ({ order: response }));
console.log(this.state.order);
}
});
}
goBack = () => this.props.history.push("/shop");
goCart = () => this.props.history.push("/cart");
handleNext = () => {
const { stepIndex } = this.state;
this.setState({
stepIndex: stepIndex + 1,
finished: stepIndex >= 2
});
};
handlePrev = () => {
const { stepIndex } = this.state;
if (stepIndex > 0) {
this.setState({ stepIndex: stepIndex - 1 });
}
};
getStepContent(stepIndex) {
let { order } = this.state;
switch (stepIndex) {
case 0:
while (!order) {
return getStepContent(0);
}
return <CartInfo CartInfo={order} />;
case 1:
return "What is an ad group anyways?";
case 2:
return "This is the bit I really care about!";
default:
return "You're a long way from home sonny jim!";
}
}
render() {
const { finished, stepIndex, order } = this.state;
const contentStyle = { margin: "0 16px" };
return (
<CartPage pageTitle="Cart" history goBack={this.goBack}>
<div className="CartHomePage">
<div style={{ width: "100%", maxWidth: 700, margin: "auto" }}>
<Stepper activeStep={stepIndex}>
<Step>
<StepLabel>Confirm your order</StepLabel>
</Step>
<Step>
<StepLabel>Where should we send it to?</StepLabel>
</Step>
<Step>
<StepLabel>Enjoy!</StepLabel>
</Step>
</Stepper>
<div style={contentStyle}>
{finished
? <p>
<a
href="#"
onClick={event => {
event.preventDefault();
this.setState({ stepIndex: 0, finished: false });
}}
>
Click here
</a>{" "}
to reset the example.
</p>
: <div>
{this.getStepContent(stepIndex)}
<div style={{ marginTop: 12 }}>
<FlatButton
label="Back"
disabled={stepIndex === 0}
onClick={this.handlePrev}
style={{ marginRight: 12 }}
/>
<RaisedButton
label={stepIndex === 2 ? "Finish" : "Next"}
primary={true}
onClick={this.handleNext}
/>
</div>
</div>}
</div>
</div>
<div>
{/* {order.map((item, i) => <div key={i}> {item.name}
{item.price} {item.quantity}</div>)} */}
{/* {this.state.order[0]} */}
</div>
</div>
</CartPage>
);
}
}
export default Cart;
This is the component I am passing it on to
import React from "react";
import { withRouter } from "react-router";
const CartInfo = ({ CartInfo }) =>
<div>
{CartInfo[0].name}
</div>;
export default withRouter(CartInfo);
This is the error code I am currently getting "CartInfo.jsx:6 Uncaught TypeError: Cannot read property 'name' of undefined at CartInfo"
It looks like you are trying to access CartInfo[0].name before the data has been fetched, which throws an error. You can change CartInfo component to something like this:
const CartInfo = ({ CartInfo }) => {
if (CartInfo[0]) {
return(
<div>
{CartInfo[0].name}
</div>;
);
}
}
This way the component will return null, then when the order data is fetched it will rerender and CartInfo[0] will not be undefined.
Another way to do this would be to use lodash _.get which returns undefined instead of throwing an error when you try to access properties of undefined.