Two components talking to each other, when adding a product to the cart (component # 1), update the setState, via service (component # 2).
When adding the product to the cart, an error returns, saying that I don't have access to the export function of component # 1.
#1 Component
import React, { useContext, useEffect } from 'react';
import Link from 'next/link';
import { Clipboard } from 'react-feather';
import { OrderCartButton } from './styles';
import OrderService from '~/services/orderservice';
import Context from '~/utils/context';
function OrderCart() {
const { state, actions } = useContext(Context);
function updateQty() {
const qty = OrderService.count();
actions({ type: 'setState', payload: { ...state, value: qty } });
}
useEffect(() => {
updateQty();
}, []);
return (
<>
<Link href="/order">
<OrderCartButton data-order-qty={state.value}>
<Clipboard />
</OrderCartButton>
</Link>
</>
);
}
export default OrderCart;
#2 Component
import { reactLocalStorage } from 'reactjs-localstorage';
import { toast } from 'react-toastify';
class OrderService {
async add(product) {
const oldorder = reactLocalStorage.getObject('_order_meumenu');
if (oldorder.length) {
const merged = [...oldorder, ...product].reduce(
(r, { id, qty, title, description, price, image }) => {
const item = r.find((q) => q.id === id);
if (item) item.qty += qty;
else r.push({ id, qty, title, description, price, image });
return r;
},
[]
);
await reactLocalStorage.setObject('_order_meumenu', merged);
} else {
await reactLocalStorage.setObject('_order_meumenu', product);
}
toast.success('Produto adicionado ao Pedido');
const qty = await this.count();
return qty;
},
async count() {
const order = reactLocalStorage.getObject('_order_meumenu');
return order.length || 0;
},
}
export default OrderService;
Component #3 - Context Moved to callback
import React, { useState, useContext } from 'react';
import { Plus, Minus } from 'react-feather';
import { ProductContainer } from './styles';
import currency from '../../utils/currency';
import OrderService from '~/services/orderservice';
import Context from '~/utils/context';
function Product(product) {
const { state, actions } = useContext(Context);
const [qty, setQty] = useState(1);
function addProductOrder(elem, elemQty) {
// eslint-disable-next-line no-param-reassign
const newElem = [];
newElem.push({ ...elem, qty: elemQty });
OrderService.add(newElem).then((val) =>
actions({ type: 'setState', payload: { ...state, value: val } })
);
}
return (
<ProductContainer>
<div className="product-image">
<img
className="image"
src={product.image}
alt={product.title}
data-product-image
/>
</div>
<div className="product-details">
<div className="product-top">
<div className="product-title">
<span className="title" data-product-title>
{product.title}
</span>
<span className="desc" data-product-desc>
{product.description}
</span>
</div>
<button
type="button"
className="product-add"
onClick={() => addProductOrder(product, qty)}
>
<span className="btn -icon -rounded" title="Add Produto">
<Plus className="icon" />
</span>
</button>
</div>
<div className="product-bottom">
<div className="product-qty">
<div className="product-control-number">
<Minus className="icon" onClick={() => setQty(qty - 1)} />
<input
className="input"
type="number"
min="1"
max="9"
value={qty}
readOnly
data-number-value
/>
<Plus className="icon" onClick={() => setQty(qty + 1)} />
</div>
</div>
<div
className="product-price"
data-product-price={product.price}
data-product-totalprice="9"
>
{currency(product.price)}
</div>
</div>
</div>
</ProductContainer>
);
}
export default Product;
There are a few things to fix:
import OrderCart from '~/components/OrderCart';
// ...
OrderCart.updateQty();
the default export in ~/components/OrderCart is the class component(OrderCart) and updateQty is another function in the same file, so the import statement should be something like:
import { updateQty } from '~/components/OrderCart';
and the usage should be
updateQty()
but this will not work because calling a function that returns some object will not cause a rerender.
So, to fix this you should pass a callback to the child component that calls the add function, and call the callback after invoking add.
The callback function to pass as props to the child can be handleUpdateQty.
Related
Here I am dispatching an action on button click but now I want to dispatch one more action from redux on the same button and the action which I want to dispatch is already imported on top named totalHandler so how am I supposed to do this thanks :)
import React from "react";
import { useParams } from "react-router-dom";
import "./ProductDetail.css";
import { useDispatch, useSelector } from "react-redux";
import { cartHandler } from "../../store/DataStore";
import { totalHandler } from "../../store/DataStore";
const Detail = () => {
const { id } = useParams();
const dispatch = useDispatch();
let data = useSelector((state) => state.data.DUMMY_DATA);
data = data.filter((val) => val.product_id === id);
data = data[0];
return (
<div className="detail_wrapper">
<div>
<img src={data.product_image} alt="" className="detail_image" />
</div>
<div className="inner">
<div className="detail_title">{data.product_title}</div>
<div className="detail_description">{data.product_description}</div>
<div className="detail_price">{data.product_price}</div>
<button
className="button"
onClick={() => dispatch(cartHandler(data.product_id))}
>
Add to Cart
</button>
</div>
</div>
);
};
export default Detail;
<button className="button" onClick={()=>{dispatch(cartHandler(data.product_id));dispatch(totalHandler())}}>Add to Cart</button>
OR create a function like
function Dispatch(){
dispatch(totalHandler());
dispatch(cartHandler(data.product_id));
}
<button className="button"
onClick={Dispatch}>Add to Cart</button>
Just add braquet at the right place {}
import React from "react";
import { useParams } from "react-router-dom";
import "./ProductDetail.css";
import { useDispatch, useSelector } from "react-redux";
import { cartHandler } from "../../store/DataStore";
import { totalHandler } from "../../store/DataStore";
const Detail = () => {
const { id } = useParams();
const dispatch = useDispatch();
let data = useSelector((state) => state.data.DUMMY_DATA);
data = data.filter((val) => val.product_id === id);
data = data[0];
return (
<div className="detail_wrapper">
<div>
<img src={data.product_image} alt="" className="detail_image" />
</div>
<div className="inner">
<div className="detail_title">{data.product_title}</div>
<div className="detail_description">{data.product_description}</div>
<div className="detail_price">{data.product_price}</div>
<button
className="button"
onClick={() => {dispatch(cartHandler(data.product_id));
dispatch(cartHandler(data.product_id_2))}}
>
Add to Cart
</button>
</div>
</div>
);
};
export default Detail;
One little precision because I have been struggling because of that detail, don't forget to await if necessary, in my case:
function handleClick(dice: DiceViewModel) {
return dice.isTenzies
? async () => {
await dispatch(initializeDice())
await dispatch(rollDice())
}
: () => dispatch(rollDice())
}
I want to change the amount of a product in the preview CMP that is rendered by the list CMP.
The preview changes the data and the list (father CMP) I set the updated data in the state but the cmps didn't re-rendering. the data is changed and the state is changed but the cmps are not.
the father CMP
import { useEffect, useState } from 'react'
import { productService } from '../services/product.service.js'
import { ProductPreview } from './ProductPreview.jsx'
import { utilService } from '../services/util.service.js'
export function ProductList() {
const [totalMoney, setTotalMoney] = useState(0)
const [spendMoney, setSpendMoney] = useState('You havent Spent yet !')
const [products, setProducts] = useState([])
useEffect(() => {
setTotalMoney(217000000000)
getProducts()
}, [])
const getProducts = () => {
setProducts(productService.getProducts())
}
if (products.length === 0) return <h1>loading...</h1>
return (
<main>
<div className="total">
<span> Remaining: {utilService.currencyFormat(totalMoney)}</span>
<span>{spendMoney}</span>
</div>
<ul className="products-list">
{products.map((product) => (
<ProductPreview
product={product}
key={product._id}
getProducts={getProducts}
/>
))}
</ul>
</main>
)
}
the Previw CMP
import { utilService } from '../services/util.service.js'
import { productService } from '../services/product.service.js'
import { useEffect, useState } from 'react'
export function ProductPreview({ product, getProducts }) {
const [currProduct, setCurrProduct] = useState(product)
const changeAmount = (diff) => {
const newProduct = productService.increasAmount(currProduct._id, diff)
setCurrProduct(newProduct)
getProducts()
}
return (
<li className="product-preview">
<img src={currProduct.img} alt="" />
<h1>{currProduct.name}</h1>
<h1>{utilService.currencyFormat(currProduct.price)}</h1>
<div className="actions">
<button onClick={() => changeAmount(-1)} className="sell">
Sell
</button>
<p>{currProduct.amount}</p>
<button onClick={() => changeAmount(1)} className="buy">
Buy
</button>
</div>
</li>
)
}
For some reason, when i render InventoryItem component inside InventoryPage component, dispatch is returned as undefined, but when i render it inside any other component it works perfectly.
Here's InventoryItem:
// REDUX
import { connect } from 'react-redux';
import { addItem } from '../../../Redux/reducers/cart/actions/cartActions.js';
// REUSABLE COMPONENTS
import { Button } from '../button/button.jsx';
export const InventoryItem = ({ drilledProps, dispatch }) => {
const { name, price, imageUrl } = drilledProps;
return (
<div className="collection-item">
<div
className="image"
style={{
background: `url(${imageUrl})`
}}
/>
<div className="collection-footer">
<span className="name">{name}</span>
<span className="price">${price}</span>
</div>
<Button
handler={() => dispatch(addItem(drilledProps))}
modifier="_inverted"
type="button"
text="ADD TO CART"
/>
</div>
);
};
export default connect(null)(InventoryItem);
When i render it here, dispatch returns undefined:
// REDUX
import { connect } from 'react-redux';
import { categorySelector } from '../../../../Redux/reducers/inventory/selectors/inventorySelectors.js';
// COMPONENTS
import { InventoryItem } from '../../../reusable-components/inventory-item/inventory-item.jsx';
const InventoryPage = ({ reduxProps: { categoryProps } }) => {
const { title: category, items } = categoryProps;
return (
<div className="collection-page">
<h2 className="title">{category}</h2>
<div className="items">
{
items.map((item) => (
<InventoryItem key={item.id} drilledProps={item}/>
))
}
</div>
</div>
);
};
const mapStoreToProps = (currentStore, ownProps) => ({
reduxProps: {
categoryProps: categorySelector(ownProps.match.params.categoryId)(currentStore)
}
});
export default connect(mapStoreToProps)(InventoryPage);
When i render it here it works perfectly:
// COMPONENTS
import InventoryItem from '../../../../reusable-components/inventory-item/inventory-item.jsx';
export const InventoryPreview = ({ title, items }) => {
return (
<div className="collection-preview">
<h1 className="title">{title}</h1>
<div className="preview">
{items
.filter((item, index) => index < 4)
.map((item) => (
<InventoryItem key={item.id} drilledProps={item} />
))}
</div>
</div>
);
};
Thanks for the help in advance!
You are importing the unconnected component, the connected component exports as default so you should do:
//not import { InventoryItem }
import InventoryItem from '../../../../reusable-components/inventory-item/inventory-item.jsx';
when you do export const InventoryItem = then you import it as import {InventoryItem} from ... but when you import the default export: export default connect(... then you import it as import AnyNameYouWant from ...
I am new to testing react-redux. I have a component named OTARequestDetails where the state of reducers is used and I am trying to access that state but getting the following error:TypeError: Cannot read property 'otaRequestItem' of undefined. I have tried many different ways but I am not able to understand why I can't access otaRequestItem. I have tested action and reducers individually and both tests are passed now I want to test the component.
Component:
import React, { useEffect, useState } from 'react'
import OTARequestCommandDetails from '../OTARequestCommandDetails'
import { otaStatusEnum } from '../../../constants'
import OTACommonHeader from '../OTACommonHeader'
import {
SectionTitle,
OTAStatusIcon,
} from '../../../common/components/SDKWrapper'
import { useSelector } from 'react-redux'
import { useAuth0 } from '#auth0/auth0-react'
import OTAModal from '../../../common/components/OTAModal'
import constants from '../../../constants'
function OTARequestDetails(props) {
const { isAuthenticated } = useAuth0()
const [isModalOpen, setIsModalOpen] = useState(false)
const otaItem = useSelector((state) => state.otaReducer.otaRequestItem)
const _constants = constants.OTAModal
useEffect(() => {
if (!isAuthenticated) {
const { history } = props
history.push({
pathname: '/dashboard/ota',
})
}
}, [])
const onQuit = () => {
setIsModalOpen(false)
}
const onAbortClick = () => {
setIsModalOpen(true)
}
let abortInfo
if (otaItem && otaStatusEnum.IN_PROGRESS === otaItem.status) {
abortInfo = (
<div>
<span className="headerLabel"></span>
<span className="infoLabel">
OTA request is in-process of being delievered.
<br />
In-progress OTAs can be aborted. Please note this is irrevertible.
<span className="link" onClick={() => onAbortClick()}>
Abort in-progress OTA
</span>
</span>
</div>
)
}
return (
<div className="otaRequestDetails">
{otaItem && (
<div>
<OTACommonHeader title={'OTA Details'} />
<div className="container">
<div className="marginBottom20px">
<SectionTitle text={constants.Forms.iccId.title} />
</div>
<div>
<span className="headerLabel">Request ID:</span>
<span>{otaItem.id}</span>
</div>
<div className="marginTop20px">
<span className="headerLabel">ICCID to OTA:</span>
<span>{otaItem.iccId}</span>
</div>
<div className="marginTop20px">
<span className="headerLabel">Date Created:</span>
<span>{otaItem.createdDate}</span>
</div>
<div className="marginTop20px">
<span className="headerLabel">Date Delivered:</span>
<span>{otaItem.createdDate}</span>
</div>
<div className="marginTop20px">
<span className="headerLabel">Created By:</span>
<span>{otaItem.requestedBy}</span>
</div>
<div className="marginTop20px">
<span className="headerLabel">OTA Status:</span>
<span>
<OTAStatusIcon otaStatus={otaItem.status} />
{otaItem.status}
</span>
</div>
{abortInfo}
<hr className="seperateLine" />
<OTARequestCommandDetails otaItem={otaItem} />
</div>
</div>
)}
{isModalOpen && (
<OTAModal
_title={_constants.title}
_description={_constants.description}
_confirmText={_constants.confirmText}
_onQuit={onQuit}
isModalOpen={isModalOpen}
/>
)}
</div>
)
}
export default OTARequestDetails
Test:
import React from 'react'
import { Provider } from 'react-redux'
import { render, cleanup } from '#testing-library/react'
import thunk from 'redux-thunk'
import OTARequestDetails from '.'
import configureStore from 'redux-mock-store'
afterEach(cleanup)
const mockStore = configureStore([thunk])
describe('OTA Request Details', () => {
test('should render', () => {
const store = mockStore({
otaRequestItem: {
id: '20af3082-9a56-48fd-bfc4-cb3b53e49ef5',
commandType: 'REFRESH_IMSIS',
iccId: '789',
status: 'DELIEVERED',
requestedBy: 'testuser#telna.com',
createdDate: '2021-04-29T07:08:30.247Z',
},
})
const component = render(
<Provider store={store}>
<OTARequestDetails />
</Provider>,
)
expect(component).not.toBe(null)
})
})
Can anyone help me where I am wrong and why can't I access reducers? Thanks in advance.
With selector:
const otaItem = useSelector((state) => state.otaReducer.otaRequestItem);
You are accessing otaRequestItem from a state.otaReducer object.
In the test your mock store has no otaReducer property in its object. Nest otaRequestItem object within a otaReducer object.
const store = mockStore({
otaReducer: {
otaRequestItem: {
id: '20af3082-9a56-48fd-bfc4-cb3b53e49ef5',
commandType: 'REFRESH_IMSIS',
iccId: '789',
status: 'DELIEVERED',
requestedBy: 'testuser#telna.com',
createdDate: '2021-04-29T07:08:30.247Z',
},
},
});
Basic gist is... the mock store just needs a valid object shape for what a consuming component will attempt to select from it.
I want to remove a component from itself. So I tried creating a function in its parent and calling it from the component
I have an array with the states and I tried unmounting by removing the component from the array.
This is the parent. unmount is the function
import React, { useEffect } from "react";
import "./list.css";
import List_item from "./listitem";
function List(prop) {
const unmount = (what_to_unmount) => {
prop.itemremove(prop.items.pop(what_to_unmount));
};
let i = 0;
if (prop.items.length === 0) {
return <div></div>;
} else {
return (
<div className="list_item">
{prop.items.map((item) => {
if (item !== "") {
return <List_item name={item} unmount={prop.unmount} />;
} else {
return <div></div>;
}
})}
</div>
);
}
}
export default List;
I want to unmount this function on a button click
import React, { useEffect } from "react";
import { useState } from "react";
import "./listitem.css";
import DeleteIcon from "#material-ui/icons/Delete";
function List_item(props) {
const [check, setcheck] = useState(false);
useEffect(() => {
let checkbox = document.getElementById(`checkbox${props.name}`);
if (checkbox.checked) {
document.getElementById(props.name).style.textDecoration = "line-through";
} else {
document.getElementById(props.name).style.textDecoration = "none";
}
});
return (
<div>
<div className="item">
<span className="text" id={`${props.name}`}>
{props.name}
</span>
<div>
|{" "}
<input
type="checkbox"
id={`checkbox${props.name}`}
checked={check}
onClick={() => setcheck(!check)}
></input>{" "}
|
<span className="delete">
<DeleteIcon
onClick={() => {
props.unmount(props.name);
}}
/>
</span>
</div>
</div>
<hr className="aitem" />
</div>
);
}
export default List_item;
But it does not work.
Please help.