I'm currently experiencing an issue where local storage is reset after trying to add an item that belongs to a different page(ex shown below) to the local storage. The local storage functionality is being used for a shopping cart feature.
It all works well when I try adding items from the same category, but once I switch the the category the local storage is reset.
A weird behavior that I also noticed is that for the first item that I try to add to the cart, I have to double click it for it to register in the local storage.
I've set the program so that only the shopping cart page needs to access the local storage.
Adding product from "Wearables category"
Going back and into the Items in the "Computers" section. Ignore sidebar
Adding item from wearable section and local storage is cleared.
Code:
App.js
class App extends Component {
userData;
constructor(props) {
super(props);
this.state = {
cart: [],
};
this.handleAddToCart = this.handleAddToCart.bind(this);
}
handleAddToCart = (productId, prodName, description, price) => {
console.log(" Handle Add to Cart Called ", productId);
console.log("->cart state: ", this.state.cart);
const holder = {
productId,
quantity: 1,
prodName,
description,
price,
};
const idx = this.indexOfProduct(productId);
if (idx == -1) {
// Product does not exist in cart
this.setState(
{
cart: [...this.state.cart, holder],
},
() => {
console.log("Updated Cart: ", this.state.cart);
}
);
} else {
let newArray = [...this.state.cart];
newArray[idx] = {
...newArray[idx],
quantity: newArray[idx].quantity + 1,
};
this.setState(
{
cart: newArray,
},
() => {
console.log("Updated Cart: ", this.state.cart);
}
);
}
localStorage.setItem("cart", JSON.stringify(this.state.cart));
};
indexOfProduct(productId) {
for (let index = 0; index < this.state.cart.length; index++) {
if (this.state.cart[index].productId == productId) return index;
}
return -1;
}
render() {
return (
<div className="App">
{/* <div className="container-fluid">
<NavBarComponent />
</div> */}
<>
<Router>
<div className="container-fluid">
<NavBarComponent />
</div>
<Switch>
<Route exact path="/sidebar">
<SideBarComponent />
</Route>
<Route exact path="/products/:category">
<ProductGridComponent />
</Route>
<Route exact path="/cart">
<ShoppingCartComponent />
</Route>
<Route exact path="/product/:id">
{/*onAddToCart={this.handleAddToCart} */}
<ProductViewComponent onAddToCart={this.handleAddToCart} />
</Route>
<Route exact path="/contact">
<ContactUsComponent />
</Route>
<Route exact path="/about-us">
<AboutUsComponent />
</Route>
<Route exact path="/">
<HomeComponent />
</Route>
</Switch>
</Router>
</>
<FooterComponent />
</div>
);
}
}
export default App;
ShoppingCartComponent.jsx
class ShoppingCartComponent extends Component {
constructor(props) {
super(props);
this.state = {
cart: [],
};
console.log("Hello Im the constructor");
}
static getDerivedStateFromProps(props, state) {
console.log("Hello Im the dState Func");
const sCart = localStorage.getItem("cart");
const parsedCart = JSON.parse(sCart);
if (sCart == null) {
return { cart: [] };
} else {
console.log("cart String mount on shopping cart: ", sCart);
console.log("cart Object at mount on shopping cart: ", parsedCart);
return { cart: parsedCart };
console.log("After appending", this.state.cart);
}
}
render() {
console.log("Shopping Cart Array at Render(): ", this.state.cart);
return (
<div className="container mt-5 p-3 rounded cart">
<div className="row no-gutters">
<div className="col-md-8">
<div className="product-details mr-2">
<div className="d-flex flex-row align-items-center">
<i className="fa fa-arrow"></i>
<button /* onClick={history.back} */>
<span className="ml-2">
<a style={{ color: "black" }}>Continue Shopping</a>
</span>
</button>
</div>
<hr />
<h6 className="mb-0">Shopping cart</h6>
<div className="d-flex justify-content-between">
<span>
You have {this.state.cart.length} items in your cart
</span>
<div className="d-flex flex-row align-items-center">
<span className="text-black-50">Sort by:</span>
<div className="price ml-2">
<span className="mr-1">price</span>
<i className="fa fa-angle-down"></i>
</div>
</div>
</div>
{this.state.cart.map((product) => (
<div className="d-flex justify-content-between align-items-center mt-3 p-2 items rounded">
<div className="d-flex flex-row">
<img
className="rounded"
src="https://i.imgur.com/QRwjbm5.jpg"
width="40"
/>
<div className="ml-2">
<span className="font-weight-bold d-block">
{product.prodName}
</span>
<span className="spec">256GB, Navy Blue</span>
</div>
</div>
...
ProductGridComponent.jsx //Where the products per categories are displayed. Sidebar is a separate component.
class ProductGridComponent extends Component {
constructor(props) {
super(props);
const windowUrl = window.location.pathname.substring(1);
console.log("window url: ", windowUrl);
this.state = {
category: windowUrl.substring(windowUrl.indexOf("/") + 1), //Please fix me, I am vulnerable to SQL Injection
products: [],
};
console.log(this.state.category);
this.handleShopButtonClick = this.handleShopButtonClick.bind(this);
}
componentDidMount() {
ProductService.getProductsByCategory(this.state.category).then((res) => {
this.setState({ products: res.data });
});
}
handleShopButtonClick(productId) {
this.props.history.push(`/product/${productId}`);
}
onAddClick() {}
render() {
return (
<>
{/* <div className="container-fluid page-body-wrapper"> */}
<div className="wrapper">
<SideBarComponent />
<div className="row" style={{ marginLeft: "5px" }}>
{this.state.products.map((product) => (
<div className="col col-md-3" style={{ marginTop: "5px" }}>
<div className="card">
<div className="d-flex justify-content-between align-items-center">
<div className="d-flex flex-row align-items-center time">
<i className=""></i>
<small className="ml-1">{product.vendorName}</small>
</div>
</div>
<div className="text-center">
<img src="https://i.imgur.com/TbtwkyW.jpg" width="250" />
</div>
<div className="text-center">
<h5>{product.prodName}</h5>
<span className="text-success">${product.price}</span>
</div>
<div>
<Link to={`/product/${product.id}`}>
<button
className="btn btn-outline-dark flex-shrink-0"
type="button"
style={{ marginLeft: "10px" }}
>
<i
className="bi-bag-fill me-1"
style={{ marginRight: "4px" }}
></i>
Buy Now
</button>
</Link>
<Link to={`/product/${product.id}`}>
<button
className="btn btn-outline-dark flex-shrink-0"
type="button"
style={{ marginLeft: "10px" }}
>
<i className=""></i>
View
</button>
</Link>
</div>
</div>
</div>
))}
{/* <img src="https://i.imgur.com/aTqSahW.jpg" width="250" /> */}
</div>
</div>
</>
);
}
}
export default ProductGridComponent;
ProductViewComponent.jsx
class ProductViewComponent extends React.Component {
constructor(props) {
super(props);
const windowUrl = window.location.pathname.substring(1);
console.log("window url for product: ", windowUrl);
this.state = {
//id: this.props.match.params.id,
id: windowUrl.substring(windowUrl.indexOf("/") + 1), //Please fix me, I am vulnerable to SQL Injection
name: "",
price: 0,
vendor: "holder vendor",
description: "",
};
console.log("ID: ", this.state.id);
}
componentDidMount() {
ProductService.getProductById(this.state.id).then((res) => {
let product = res.data;
this.setState({
name: product.prodName,
price: product.price,
vendor: product.vendorName,
description: product.description,
});
});
}
render() {
return (
...
<button
className="btn btn-outline-dark flex-shrink-0"
type="button"
style={{ marginLeft: "10px" }}
onClick={() =>
this.props.onAddToCart(
this.state.id,
this.state.name,
this.state.description,
this.state.price
)
}
>
<i className="bi-cart-fill me-1"></i>
Add to cart
</button>
...
I was struggling with a similar problem earlier today. My localStorage was getting rewritten upon refresh even though localStorage is often used to carry data over between refreshes / browser closures.
The problem that I realized was that I was setting the localStorage to the state upon render, and my state was initialized to an empty value. It looks like you are only calling localStorage.setItem() once in your code, and it is setting it to the state. The problem is that when localStorage.setItem() is called, your state is still an empty array.
React's this.setState() method is an asynchronous method, meaning that it will be added to a stack to be run. It gives no promises on when it will start being run or when it will finish. It looks like you are calling this.setState() right before you call localStorage.setItem(), which means that it is not updating the state in time before you are changing the localStorage.
What I would suggest is putting the call to localStorage inside of the callback function that is the second parameter of this.setState(). This callback function is always run after the state has been set.
Your state setter will look like this:
this.setState(
{
cart: [...this.state.cart, holder],
},
() => {
console.log("Updated Cart: ", this.state.cart);
localStorage.setItem("cart", JSON.stringify(this.state.cart));
}
);
The issue was a logical error. On reload, the state(cart) becomes empty. However, I never set the state to the items already on local storage before trying add the new items to the cart.
The way I did the adding functionality involves taking the current state and appending the new items, and then setting the local storage afterwards. Meaning that if the state is empty then then trying to add a new item this way will simply result in only the new item being on the cart.
I added this piece to App.js to solve the error.
componentDidMount() {
const sCart = localStorage.getItem("cart");
const parsedCart = JSON.parse(sCart);
if (sCart == null) {
this.setState({ cart: [] });
} else {
console.log("cart String mount on shopping cart: ", sCart);
console.log("cart Object at mount on shopping cart: ", parsedCart);
this.setState(
{
cart: parsedCart,
}
);
}
}
Related
I have a parent functional component named Dashboard and a child class component named DashboardTable. I'm making a graphql call in the parent class and want to pass the result into the child like this <DashboardTable data={opportunityData}/>.
problem: I can get see the data in the parent but its not showing in the child
Here is my code. Please let me know what I'm doing wrong
Dashboard
import React, { useEffect, useState } from "react";
import "bootstrap/js/src/collapse.js";
import DashboardTable from "../DashboardTable";
import { API } from "#aws-amplify/api";
import config from "../../aws-exports";
import * as queries from "../../graphql/queries";
export default function Dashboard() {
API.configure(config);
async function asyncCall() {
const gqlreturn = await API.graphql({
query: queries.listMockOppsTables,
});
//console.log(gqlreturn.data.listMockOppsTables); // result: { "data": { "listTodos": { "items": [/* ..... */] } } }
return gqlreturn;
}
const [opportunityTable, changeOpportunityTable] = useState(asyncCall());
console.log(opportunityTable); // this works! returns a promise
return (
<div>
<section className="py-5 mt-5">
<div className="container py-5">
<h2 className="fw-bold text-center">
Your upcoming shadowing events
<br />
<br />
</h2>
<DashboardTable data={opportunityTable}></DashboardTable>
</div>
</section>
</div>
);
}
DashboardTable
import React from "react";
import "bootstrap/js/src/collapse.js";
import Navigation from "../Navigation";
import { Link } from "react-router-dom";
import { API } from "#aws-amplify/api";
import config from "../../aws-exports";
import * as queries from "../../graphql/queries";
export class DashboardTable extends React.Component {
constructor() {
super();
this.state = {
opportunityData: this.props,
};
}
render() {
console.log(this.opportunityData); // this doesnt work :( no data
return (
<div>
<div
className="row row-cols-1 row-cols-md-2 mx-auto"
style={{ maxWidth: 900 }}
>
{this.opportunityData.map((opportunity) => (
<div className="col mb-4">
<div>
<a href="#">
<img
className="rounded img-fluid shadow w-100 fit-cover"
src="assets/img/products/awsLogo.jpg"
style={{
height: 250,
}}
/>
</a>
<div className="py-4">
<span
className="badge mb-2"
style={{ margin: 2, backgroundColor: "#ff9900" }}
>
{opportunity.interview_type}
</span>
<span
className="badge bg mb-2"
style={{ margin: 2, backgroundColor: "#ff9900" }}
>
{opportunity.level}
</span>
<span
className="badge bg mb-2"
style={{ margin: 2, backgroundColor: "#ff9900" }}
>
{opportunity.ShadowReverse}
</span>
</div>
</div>
</div>
))}
</div>
</div>
);
}
}
export default DashboardTable;
Few pointers
Call api on mount in parent's useEffect
In child directly use the passed property in child
function Dashboard() {
API.configure(config);
async function asyncCall() {
const gqlreturn = await API.graphql({
query: queries.listMockOppsTables,
});
//console.log(gqlreturn.data.listMockOppsTables); // result: { "data": { "listTodos": { "items": [/* ..... */] } } }
return gqlreturn;
}
// initialize with empty array
const [opportunityTable, changeOpportunityTable] = useState([]);
console.log(opportunityTable); // this works! returns a promise
// call api to fetch data on mount
useEffect(( => {
const fetchData = async () => {
const response = await asyncCall();
changeOpportunityTable(response)
}
fetchData()
}, [])
return (
<div>
<section className="py-5 mt-5">
<div className="container py-5">
<h2 className="fw-bold text-center">
Your upcoming shadowing events
<br />
<br />
</h2>
<DashboardTable data={opportunityTable}></DashboardTable>
</div>
</section>
</div>
);
}
class DashboardTable extends React.Component {
constructor() {
super();
//this.state = {
// opportunityData: this.props,
//};
}
render() {
console.log(this.props.data); // this doesnt work :( no data
return (
<div>
<div
className="row row-cols-1 row-cols-md-2 mx-auto"
style={{ maxWidth: 900 }}
>
//map thru data prop {this.props.data?.map((opportunity) => (
<div className="col mb-4">
<div>
<a href="#">
<img
className="rounded img-fluid shadow w-100 fit-cover"
src="assets/img/products/awsLogo.jpg"
style={{
height: 250,
}}
/>
</a>
<div className="py-4">
<span
className="badge mb-2"
style={{ margin: 2, backgroundColor: "#ff9900" }}
>
{opportunity.interview_type}
</span>
<span
className="badge bg mb-2"
style={{ margin: 2, backgroundColor: "#ff9900" }}
>
{opportunity.level}
</span>
<span
className="badge bg mb-2"
style={{ margin: 2, backgroundColor: "#ff9900" }}
>
{opportunity.ShadowReverse}
</span>
</div>
</div>
</div>
))}
</div>
</div>
);
}
}
Hope it helps
There are some bugs in the child like this.state.opportunityData = this.props, that end part should likely be this.props.opportunityData, however to get you going with the async call in the parent component give this a try
const [opportunityTable, changeOpportunityTable] = useState([]);
async function asyncCall() {
const gqlreturn = await API.graphql({
query: queries.listMockOppsTables,
});
changeOpportunityTable(gqlreturn);
}
useEffect(() => asyncCall(), []);
I am trying to set my state in component did Mount lifecycle method in order to check the data I am getting from local storage and taking decision either to direct the user to the login page or keep remain the user on the dashboard if my local storage data exists in my local storage.
Or is there any way I can prevent switching the page to login on refreshing the web page and remain on the same even after reloading the browser if I have my data in local storage.
Thanks in advance!
import React, { Component } from "react";
import { Row, Col, Input, Button, Alert, Container, Label } from "reactstrap";
// Redux
import { connect } from "react-redux";
import { withRouter, Link, Route, Redirect } from "react-router-dom";
// availity-reactstrap-validation
import { AvForm, AvField } from "availity-reactstrap-validation";
// actions
import { checkLogin, apiError } from "../../store/actions";
import { loginAction } from "../../redux/actions/authActions";
// import images
import logodark from "../../assets/images/logo-dark.png";
import logolight from "../../assets/images/logo-light.png";
class Login extends Component {
constructor(props) {
super(props);
this.state = {
username: "test#gmail.com",
password: "246810",
};
this.handleSubmit = this.handleSubmit.bind(this);
}
async handleSubmit(event, values) {
this.props.checkLogin(values, this.props.history);
const { username, password } = this.state;
localStorage.setItem("username", username);
localStorage.setItem("password", password);
let action = await this.props.loginAction(values, () => {
this.props.history.push({
pathname: "/dashboard",
});
});
}
// username = localStorage.getItem("username");
// password = localStorage.getItem("password");
// if (window.performance.getEntriesByType("navigation")[0].type).toSring() === 'back_forward') {
// window.location.reload()
// }
updateUser = (username, password) => {
this.setState({ username: username, password: password });
};
componentDidMount() {
// this.setState({
// // set username and password from local storage
// username: JSON.parse(localStorage.getItem("username")),
// password: JSON.parse(localStorage.getItem("password")),
// });
const username = localStorage.getItem("username");
const password = localStorage.getItem("password");
this.props.apiError("");
document.body.classList.add("auth-body-bg");
console.log("on page reload", window.performance.navigation);
if (window.performance.navigation.type !== 0) {
if (username && password) {
this.props.history.push({
pathname: "/dashboard",
});
} else {
this.props.history.push({
pathname: "/login",
});
}
}
}
componentWillUnmount() {
document.body.classList.remove("auth-body-bg");
}
render() {
return (
<React.Fragment>
<div>
<Container fluid className="p-0">
<Row className="g-0">
<Col lg={4}>
<div className="authentication-page-content p-4 d-flex align-items-center min-vh-100">
<div className="w-100">
<Row className="justify-content-center">
<Col lg={9}>
<div>
<div className="text-center">
<div>
<Link to="/" class="">
<img
src={logodark}
alt=""
height="20"
class="auth-logo logo-dark mx-auto"
/>
<img
src={logolight}
alt=""
height="20"
class="auth-logo logo-light mx-auto"
/>
</Link>
</div>
<h4 className="font-size-18 mt-4">
Welcome Back !
</h4>
{/* <p className="text-muted">Sign in to continue to Nazox.</p> */}
</div>
{/* {this.props.loginError && this.props.loginError ? <Alert color="danger">{this.props.loginError}</Alert> : null} */}
<div className="p-2 mt-5">
<AvForm
className="form-horizontal"
onValidSubmit={this.handleSubmit}
>
<div className="auth-form-group-custom mb-4">
<i className="ri-user-2-line auti-custom-input-icon"></i>
<Label htmlFor="username">Email</Label>
<AvField
name="username"
value={this.state.username}
type="text"
className="form-control"
id="username"
validate={{ email: true, required: true }}
placeholder="Enter username"
/>
</div>
<div className="auth-form-group-custom mb-4">
<i className="ri-lock-2-line auti-custom-input-icon"></i>
<Label htmlFor="userpassword">Password</Label>
<AvField
name="password"
value={this.state.password}
type="password"
className="form-control"
id="userpassword"
placeholder="Enter password"
/>
</div>
{/* <div className="form-check">
<Input type="checkbox" className="form-check-input" id="customControlInline" />
<Label className="form-check-label" htmlFor="customControlInline">Remember me</Label>
</div> */}
<div className="mt-4 text-center">
<Button
color="primary"
className="w-md waves-effect waves-light"
type="submit"
>
Log In
</Button>
</div>
{/* <div className="mt-4 text-center">
<Link to="/forgot-password" className="text-muted"><i className="mdi mdi-lock me-1"></i> Forgot your password?</Link>
</div> */}
</AvForm>
</div>
<div className="mt-5 text-center">
<p>
Don't have an account ?{" "}
<Link
to="/register"
className="fw-medium text-primary"
>
{" "}
Register{" "}
</Link>{" "}
</p>
{/* <p>© 2021 Nazox. Crafted with <i className="mdi mdi-heart text-danger"></i> by Themesdesign</p> */}
</div>
</div>
</Col>
</Row>
</div>
</div>
</Col>
<Col lg={8}>
<div className="authentication-bg">
<div className="bg-overlay"></div>
</div>
</Col>
</Row>
</Container>
</div>
</React.Fragment>
);
}
}
const mapStatetoProps = (state) => {
const { loginError } = state.Login;
return { loginError };
};
export default withRouter(
connect(mapStatetoProps, { checkLogin, apiError, loginAction })(Login)
);
You should consider to implement some Error Boundaries (if using React 16+);
If errors are not caught by any error boundary, this will result in unmounting of the whole component tree (https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html).
If the unmounting in turn changes something inside the store, this will cause re-rendering again in an infinite loop.
One error could occur (and probably not caught if you don't have Error Boundaries) beacuse you're using JSON.parse(localStorage.getItem("username")) in setState().
If the item inside localStorage is a normal string (as probably is), JSON.parse(<string>) will result in an error.
I am trying to set a simple search operation in a user interface as shown below:
I have a total of 70 react-strap cards and each card contain a vessel with name, type and an image. I would like to search the name of the vessel and have the card related to that vessel to pop-up. All my images are currently contained inside the external database Contentful. Below the fields of interests:
The problem is that I don't know how to write a search function that locate a specific value of a list.
Below the code:
SideBar.js
import React from 'react';
import Client from '../Contentful';
import SearchVessel from '../components/SearchVessel';
class Sidebar extends React.Component {
state = {
ships: [],
};
async componentDidMount() {
let response = await Client.getEntries({
content_type: 'cards'
});
const ships = response.items.map((item) => {
const {
name,
slug,
type
} = item.fields;
return {
name,
slug,
type
};
});
this.setState({
ships
});
}
getFilteredShips = () => {
if (!this.props.activeShip) {
return this.state.ships;
}
let targetShip = this.state.ships.filter(
(ship) => this.props.activeShip.name === ship.name
);
let otherShipsArray = this.state.ships.filter((ship) => this.props.activeShip.name !== ship.name);
return targetShip.concat(otherShipsArray);
};
render() {
return (
<div className="map-sidebar">
{this.props.activeShipTypes}
<SearchVessel />
<pre>
{this.getFilteredShips().map((ship) => {
console.log(ship);
return (
<Card className="mb-2">
<CardImg />
<CardBody>
<div className="row">
<img
className="image-sizing-primary"
src={ship.companylogo.fields.file.url}
alt="shipImage"
/>
</div>
<div>
<img
className="image-sizing-secondary"
src={ship.images.fields.file.url}
alt="shipImage"
/>
</div>
<CardTitle>
<h3 className="thick">{ship.name}</h3>
</CardTitle>
<CardSubtitle>{ship.type}</CardSubtitle>
<CardText>
<br />
<h6>Project Details</h6>
<p>For a description of the project view the specification included</p>
</CardText>
<Row style={{ marginTop: '20px' }}>
<div className="buttoncontainer">
<div className="btn btn-cards">
<a
className="buttonLink"
download
href={ship.projectnotes.fields.file.url}
>
Project Notes
</a>
</div>
<div className="btn btn-cards">
<a className="buttonLink" href={ship.abstract.fields.file.url}>
Abstract
</a>
</div>
</div>
</Row>
</CardBody>
</Card>
);
})}
</pre>
</div>
);
}
}
export default Sidebar;
VesselSearch.js
import React, { Component } from 'react';
export default class SearchVessel extends Component {
render() {
const { value, handleSubmit, handleChange } = this.props;
return (
<React.Fragment>
<div className="container">
<div className="row">
<div className="col-10 mx-auto col-md-8 mt-5 text-center">
<h4 className="text-slanted text-capitalize">Search for Vessel</h4>
<form className="mt-4" onSubmit={handleSubmit}>
<label htmlFor="search" className="text-capitalize">
type vessel separated by comma
</label>
<div className="input-group">
<input
type="text"
name="search"
placeholder="Type name of vessel here"
className="form-control"
value={value}
onChange={handleChange}
/>
<div className="input-group-append">
<button type="submit" className="input-group-text bg-primary text-white">
<i className="fas fa-search" />
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</React.Fragment>
);
}
}
What I have done so far:
1) I tried different combination with the filter function and I think I am close. The problem is that when I operate the search nothing happens and in order to find the card of the vessel I want, I have to scroll down until I find it.
I am running out of ideas and if you see something I didn't catch point me in the right direction for solving this issue.
You're close! I would add a field to your state called 'searchText' and then create a method to filter based on that searchText state item.
getFilteredShips = () => this.state.ships.filter(s => s.name.includes(this.state.searchText)
Then just map over those values to render the cards that match the search text. The cards will update each time the searchText value updates.
this.getFilteredShips().map(ship => ..........
React is famous for re-usable component. You will have all the data of these vessels in an array. You will loop through the array and render the items with card component.And when you search for the specific card you want that vessel to pop out on top.
There are two ways to do it:
You have to run through the array, find the index of that vessel and do whatever it takes to manipulate your array and to make that item at top and re-render your list.
Alternatively render one more component on top of your vessel list as user clicks the search button. You just have to find the item index and render it. This way you don't have to deal with array manipulation. It doesn't matter if you have 80 or 1000 cards.
Please checkout official documentation for array methods, for array slicing and splice.
Hope this is what you are looking for. If you need further help, comment please.
I'm a bit new to React and I've been having some problems understanding the workarounds of certain methods that I've used in other languages.
The Problem:
What I was hoping to achieve is that whenever the user clicks a button, the app will render a new section displaying some variable values. What I did was that when the user clicked a button, an state changed, and let the new Component render, and I passed the data through its props.
The problem is, If I understand correctly, that I'm passing the old values when I create the component and not the actual/updated values that I want to render...
Let's say I have this following variables.
const user_data = {
pic_url: 'null',
followers: 'Loading...',
followings: 'Loading...',
nTweets: 'Loading...',
};
Those variables are going to change value whenever the user click a button.
This next block of code is what I use to render the next component where I want the new values.
const SomeComponent = props => {
const [resolved, setResolved] = useState({ display: false });
const displayValidation = props => {
setResolved({ ...resolved, display: !resolved.display });
};
function getData(username) {
const url = 'https://www.twitter.com/' + username;
getHTML(url)
.then(res => {
getUserData(res).then(res => {
user_data.followers = res.followers;
user_data.followings = res.followings;
user_data.nTweets = res.nTweets;
user_data.pic_url = res.pic_url;
console.log('Updated data:', user_data);
displayValidation();
});
})
.catch(function(error) {
console.error('Username was not found.');
});
}
const handleSubmit = event => {
event.preventDefault();
console.log('Resolving data...');
getData(user.username);
};
return (
<React.Fragment>
<Navbar />
<div className="container lg-padding">
<div className="row" id="getTracker">
<div className="col-sm-12 center">
<div className="card text-center hoverable">
<div className="card-body">
<div className="input-field">
<i className="material-icons prefix">account_circle</i>
<input
id="username"
type="text"
className="validate"
value={user.username}
onChange={handleChange}
/>
<label htmlFor="username">Enter a username to track</label>
</div>
<input
type="button"
onClick={handleSubmit}
value="Track"
className="btn-large blue darken-4 waves-effect waves-light"
/>
</div>
</div>
</div>
</div>
<div className="row">
<div className="col-sm-12">
**{resolved.display && <DisplayData type={1} data={user_data} />}**
</div>
</div>
</div>
<Footer />
</React.Fragment>
);
};
I want to see the new values, but it always render the first values that I passed when creating the component.
This is the component that I create
import React from 'react';
const DisplayData = props => {
const user = props.data;
console.log('Display', user);
switch (props.type) {
case 1: //Twitter
return (
<React.Fragment>
<div className="row lg-padding">
<div className="col-sm-12 lg-padding center">
<img
src={user.pic_url}
alt="profile_picture"
style={{ width: 50 + '%' }}
/>
</div>
<h2>{user.username}</h2>
</div>
<div className="row lg-padding">
<div className="col-sm-4">
<h4>Tweets: {user.nTweets}</h4>
</div>
<div className="col-sm-4">
<h4>Followers: {user.followers}</h4>
</div>
<div className="col-sm-4">
<h4>Followings: {user.followings}</h4>
</div>
</div>
</React.Fragment>
);
case 2: //Instagram
return <React.Fragment />;
default:
return (
<React.Fragment>
<div className="row lg-padding">
<div className="col-sm-12 lg-padding center">
<img
src={user.pic_url}
alt="profile_picture"
style={{ width: 50 + '%' }}
/>
<h2>Instagram_User</h2>
<h4>Posts: ND</h4>
<h4>Followers: ND</h4>
<h4>Followings: ND</h4>
</div>
</div>
</React.Fragment>
);
}
};
export default DisplayData;
How can I update the data in the component or render the component when the data is updated?
Maybe your user_data might to be a state object.
// Somecomponent ...
const [user_data, setUser_data] = useState({
pic_url: 'null',
followers: 'Loading...',
followings: 'Loading...',
nTweets: 'Loading...'
})
/* Rest of stuff */
const handleSubmit = async event => {
/*...*/
const userData = await getData(user.username)
setUser_data(userData)
}
// Then display the stated-stored user_data
<div className="col-sm-12">
**{resolved.display && <DisplayData type={1} data={user_data} />}**
</div>
I'm using redux for state management in my app. But after I dispatch an action only the parent component updates the children doesn't receive the new props or updates. I'm using react 16.2.0, Redux 3.7.2, react-redux 5.0.6. These are my components:
Parent component
class ConnectingItems extends React.Component{
constructor(props){
super(props);
this.afterProds = this.afterProds.bind(this);
this.handleSearch = this.handleSearch.bind(this);
this.data = this.props.data.products;
}
componentDidUpdate(){
this.data = this.props.data.products;
}
afterProds(){
this.data = this.props.data.products;
this.forceUpdate();
}
handleSearch(data){
this.data = data;
this.forceUpdate();
}
render(){
const products = this.props.data.products;
const searched_products = this.data;
console.log('rerendering in main')
return(
<div>
<Navbar/>
<div className="container">
<h5 className="center-align">
<Searchform data={products} new_data={this.handleSearch}/>
</h5>
</div>
<ProductsList user_data={searched_products}/>
<Modal
header='Add Product'
modalOptions={{
opacity: .0
}}
trigger={
<div className='fixed-action-btn action-button'>
<a className="btn-floating btn-large yellow darken-1">
<i className="fa fa-plus"></i>
</a>
</div>
}>
<AddProducts afterProdAdd={this.afterProds}/>
</Modal>
</div>
);
}
}
Child componenet:
class ConnectingProductListing extends React.Component{
constructor(props){
super(props);
this.handleDelete = this.handleDelete.bind(this);
this.afterEdit = this.afterEdit.bind(this);
}
handleDelete(id){
this.props.deleteProduct(id);
console.log('delete dispatched');
this.forceUpdate();
}
componentWillReceiveProps(newprops){
console.log(newprops);
}
afterEdit(){
this.forceUpdate();
}
render(){
let data = this.props.user_data;
console.log('im re rendering')
console.log(data);
return(
<div className="container section">
<div className="row">
<div className="col s12">
{data.length < 1 ?
<div className="col s12 center-align">
<p>
<b>No Product here.</b>
</p>
</div>:
data.map(product => {
const name = product.name;
const quantity = product.quantity;
const price = product.price;
return(
<div key={product.id} className="card center grey lighten-5 z-depth-1">
<div className='card-content left-align'>
<span className='card-title'>
{name}
</span>
<span>
Price: ${price}<br/>
</span><span>
Quantity: {quantity}
</span>
</div>
<div className='card-action center'>
<div className='row'>
<div className='col s12'>
<Modal
header='Edit Product'
modalOptions={{
opacity: 0.0
}}
trigger={
<button className='btn yellow accent-3 center'>Edit</button>
}
actions={
<div>
<Modal
header='Delete Product'
modalOptions={{
opacity: 0.0
}}
trigger={
<a className="modal-action modal-close waves-effect waves-yellow btn-flat">Delete</a>
}
actions={
<div>
<a className='modal-action modal-close waves-effect waves-yellow btn-flat'
onClick={() => this.handleDelete(product.id)}>Yes</a>
<a className='modal-action modal-close waves-effect waves-yellow btn-flat'>No</a>
</div>
}>
<p>Are you sure you want to delete this product? It can't be undone</p>
</Modal>
<a className="modal-action modal-close waves-effect waves-yellow btn-flat">Close</a>
</div>
}>
<EditProducts product={product} afterEdit={this.afterEdit}/>
</Modal>
</div>
</div>
</div>
</div>
);
})
}
</div>
</div>
</div>
);
}
}
My reducer:
const rootReducer = (state = initialState, action) => {
switch (action.type){
case constants.ADD_PRODUCT:
return {
...state,
data: {
...state.data,
products: [
...state.data.products,
action.payload
]
}
}
default:
return state;
}
};
export default rootReducer;
Initial state:
initialState = {
data: {
id: 0,
name: 'John Doe',
email: 'johndoe#gmail.com',
products: [
{
id: 0,
name: 'product name',
price: 10,
quantity: 10,
}
],
}
};
store:
import { createStore } from "redux";
import rootReducer from "../reducer/index";
const store = createStore(
rootReducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
export default store;
also i'm only focusing on the add product action
Please guys help me
UPDATE: I see you added some code, but you're still missing the container? And your action (constant) definition code?
If you're using redux, then you should include the source code for your store and containers.
Most likely the issue is the first one at https://web.archive.org/web/20180304224831/https://redux.js.org/troubleshooting#nothing-happens-when-i-dispatch-an-action:
Redux assumes that you never mutate the objects it gives to you in the reducer. Every single time, you must return the new state object.
There are various suggestions there. If you include more code, I might be able to help more.