I am making a watchlist component using redux/react/hooks. Component is in 3 pieces (form to add to list, container to map over securities, and security to display each item)
So far I have been able to add values by dispatching addStock in the form component. I tried to do the same thing in security component for deleteStock but it isn't working/ reloads page.
reducer:
const stockSelector = (state = STOCK_STATE, action) => {
switch (action.type) {
case STOCK_SELECTED:
return action.payload;
case FETCH_STOCK_LIST:
return { ...state, watchlist: action.payload.select, title: action.payload.name, loading: false};
case STOCK_LIST_LOADING:
return {...state, loading: true}
case ADD_STOCK:
return { ...state, watchlist: [action.payload, ...state.watchlist] };
case DELETE_STOCK:
return {
...state,
watchlist: [
...state.watchlist.slice(0, action.payload),
...state.watchlist.slice(action.payload + 1)
],
};
default:
return state;
}
action
export const deleteStock = (payload) => ({
type: DELETE_STOCK,
payload,
});
watchlist component
const Watchlist = ({selected, watchlists, number}) => {
const dispatch = useDispatch();
const [taskList, setTaskList] = useState(['AAPL', 'MSFT', 'AMZN'])
const [list, setList] = useState(selected)
const [allList, setAllList] = useState(watchlists)
const [selectNumber, setSelectNumber] = useState(number)
const selectWatchlist = async () => {
setList(selected)
setSelectNumber(number)
}
useEffect(() => {
selectWatchlist()
.then(dispatch(fetchStockList(selectNumber)))
}, []);
return (
<React.Fragment>
<Col className="watchlist-master-col">
<Row className="watchlist-form-row">
<Col>
<AddWatchlistForm className="watchlist-form" />
</Col>
</Row>
<Row className="watchlist-list-row">
<ListSecurityContainer
list={taskList}
className="watchlist-list"
number={number}
/>
</Row>
<Row>
<Modal className='modalOne' />
</Row>
</Col>
<Modal />
</React.Fragment>
)
}
const mapStateToProps = (state) => {
console.log(state)
return {
selected: state.Watchlist.stock.title,
watchlists: state.Watchlist.watchlist.watchlist,
watchlist: state.Watchlist.stock.watchlist,
number: state.Watchlist.watchlist.number,
}
}
container
const ListSecurityContainer = ({loading, stocks}) => {
const dispatch = useDispatch();
const handleCloseTask = (id) => {
dispatch(deleteStock(id))
}
if (loading === false) {
return (
<React.Fragment>
<Col>
{stocks.map((value, index) => (
<Security
key={stocks[index]}
id={index}
{...value}
name={value}
// onClose={handleCloseTask}
className="security-elem"
/>
))}
</Col>
</React.Fragment>
);
}
return <div>Loading...</div>
}
const mapStateToProps = (state) => {
console.log(state.Watchlist.stock.watchlist)
return {
stocks: state.Watchlist.stock.watchlist,
loading: state.Watchlist.stock.loading
}
}
security
const Security = (value) => {
const dispatch = useDispatch();
const [taskName, setTaskName] =useState(value.name)
const removeTask = () => {
dispatch(deleteStock(taskName))
}
return (
<div className="list-group-item">
{value.name}
<button onClick={removeTask()} style={{ float: 'right' }}>
<i className="glyphicon glyphicon-remove"></i>
</button>
</div>
);
}
Fixed this by correcting the issues listed in the comments and also fixing a type that I had in my constants.js file.
const Security = ({index, name}) => {
const dispatch = useDispatch();
const [taskName, setTaskName] =useState(name)
const removeTask = (e) => {
e.stopPropagation()
dispatch(removeStock(index))
}
return (
<Row className="list-group-item">
<div className="item-titles">
{name}
</div>
<button onClick={() => dispatch(removeStock(index))} className="remove-item">
<i className="glyphicon glyphicon-remove"></i>
</button>
</Row>
);
}
Related
Faced a problem with incorrect syntax when executing the get method to the database. The error is due to an attempt to put string data into integer, but I can't figure out where it's happening.
I think this is happening somewhere in the code below, please help me find where:
`
const Admin = () => {
const [fumoVisible, setFumoVisible] = useState(false);
const [searchFumo, setSearchFumo] = useState("");
const [searchedFumo, setSearchedFumo] = useState([]);
useEffect(() => {
getAllFumoInAdminPage(searchFumo, currentPage, filter).then(
({ count, rows }) => {
setSearchedFumo(rows);
setCount(count);
}
);
}, [currentPage]);
useEffect(() => {
getAllFumoInAdminPage(searchFumo, 1, filter).then(({ count, rows }) => {
setSearchedFumo(rows);
setCount(count);
setCurrentPage(1);
});
}, [filter]);
const fetchFumo = () => {
getAllFumoInAdminPage(searchFumo, currentPage, filter).then(
({ count, rows }) => {
setSearchedFumo(rows);
setCount(count);
}
);
};
return (
<Container className="d-flex flex-column">
<InputGroup className="mb-3">
<Form.Control
aria-label="Default"
aria-describedby="inputGroup-sizing-default"
value={searchFumo}
onChange={(e) => setSearchFumo(e.target.value)}
placeholder="Enter name..."
/>
<Button onClick={fetchFumo} variant="outline-dark" className="ml-2">
Search
</Button>
</InputGroup>
<ListGroup>
{searchedFumo &&
searchedFumo.map(({ id, img, price, name }) => {
return (
<ListGroup.Item className="mt-3" key={id}>
//
</ListGroup.Item>
);
})}
</ListGroup>
</Container>
);
};
export default Admin;
`
In my project I want to access the name of student after clicking the present button .
So it means if I click on "arijit" name then it should log on console "arijit"
const StudentList = () => {
const [studentlist, setStudentList] = useState([]);
const studentdataList = async () => {
const result = await fetch("http://localhost:5000/api/students");
const studentResult = await result.json();
setStudentList(studentResult);
console.log(studentResult[0].name);
};
function present(){
// what to write here?
}
useEffect(() => {
studentdataList();
},[]);
return (
<div>
{studentlist.map((student) => {
return (
<Card style={{ width: "18rem" }}>
<Card.Body>
<Card.Title> {student.name} </Card.Title>
<Button onClick={present}>present</Button>
<Button>abscent</Button>
</Card.Body>
</Card>
);
})}
</div>
);
};
export default StudentList;
The simplest solution would be to pass the student's name into the present function inside of the onClick handler.
const StudentList = () => {
const [studentlist, setStudentList] = useState([]);
const studentdataList = async () => {
const result = await fetch("http://localhost:5000/api/students");
const studentResult = await result.json();
setStudentList(studentResult);
console.log(studentResult[0].name);
};
function present(name){
console.log(name);
}
useEffect(() => {
studentdataList();
},[]);
return (
<div>
{studentlist.map((student) => {
return (
<Card style={{ width: "18rem" }}>
<Card.Body>
<Card.Title> {student.name} </Card.Title>
<Button onClick={() => present(student.name)}>present</Button>
<Button>abscent</Button>
</Card.Body>
</Card>
);
})}
</div>
);
};
export default StudentList;
I've been having an issue with react not rendering the updated cartItems in the state. The state updates perfectly fine but the items that I delete don't actually get removed from the rendering.
I'm very new to react, redux and state management but I assume nothing's actually wrong with the state itself, rather how/when the items are being iterated to be rendered. It's only when I refresh the page that it renders accurately.
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { Row, Col, ListGroup, Image, Button } from "react-bootstrap";
import Message from "../components/Message";
import { addToCart, removeFromCart } from "../actions/cartActions";
function Basket({ match, location, history }) {
const productId = match.params.id;
const qty = location.search ? Number(location.search.split("?")[1]) : 1;
const specID = location.search ? Number(location.search.split("?")[2]) : 0;
const dispatch = useDispatch();
const cart = useSelector((state) => state.cart);
const { cartItems } = cart;
useEffect(() => {
if (productId) {
dispatch(addToCart(productId, qty, specID));
}
}, [dispatch, productId, qty, specID]);
const removeFromCartHandler = (cartItem) => {
dispatch(removeFromCart(cartItem));
};
const checkoutHandler = () => {
history.push("/login?redirect=shipping");
};
return (
<Row>
<Col md={8}>
{cartItems.length === 0 ? (
<Message variant="primary">
Your cart is empty{" "}
<Link to="/">
<strong>Go Back</strong>
</Link>
</Message>
) : (
<div>
<Link to="/">
<strong>Go Back</strong>
</Link>
<ListGroup variant="flush">
{cartItems.map((item) => (
<ListGroup.Item key={item.product} className="cart">
<Row>
{/* <Col xs={4} md={2}>
<Image src={item.img} alt={item.name} fluid rounded />
</Col> */}
<Col xs={4} md={3}>
<Link to={`/product/${item.product}`}>{item.name}</Link> (
{item.size})
</Col>
<Col xs={2} md={2}>
£{item.price}
</Col>
<Col xs={6} md={3}>
Qty
</Col>
<Col xs={6} md={1}>
<Button
type="button"
variant="light"
onClick={() => removeFromCartHandler(item)}
>
<i className="fas fa-trash" />
</Button>
</Col>
</Row>
</ListGroup.Item>
))}
</ListGroup>
</div>
)}
</Col>
</Row>
);
}
export default Basket;
The reducer for the cart.
import { CART_ADD_ITEM, CART_REMOVE_ITEM } from "../constants/cartConstants";
export const cartReducer = (
state = { cartItems: [{ specList: [{ price: [] }] }] },
action
) => {
switch (action.type) {
case CART_ADD_ITEM:
const item = action.payload;
const existItem = state.cartItems.find(
(x) => x.product === item.product && x.size === item.size
);
if (existItem) {
return {
...state,
cartItems: state.cartItems.map((x) =>
x.product === existItem.product ? item : x
),
};
} else {
return { ...state, cartItems: [...state.cartItems, item] };
}
case CART_REMOVE_ITEM:
return {
...state,
cartItems: state.cartItems.filter((x) => x !== action.payload),
};
default:
return state;
}
};
import axios from "axios";
import { CART_ADD_ITEM, CART_REMOVE_ITEM } from "../constants/cartConstants";
export const addToCart = (id, qty) => async (dispatch, getState) => {
const { data } = await axios.get(`/api/products/${id}`);
dispatch({
type: CART_ADD_ITEM,
payload: {
product: data._id,
name: data.name,
image: data.image,
price: data.price,
countInStock: data.countInStock,
qty,
},
});
localStorage.setItem("cartItems", JSON.stringify(getState().cart.cartItems));
};
export const removeFromCart = (id) => async (dispatch, getState) => {
dispatch({
type: CART_REMOVE_ITEM,
payload: id,
});
localStorage.setItem("cartItems", JSON.stringify(getState().cart.cartItems));
};
You are calling () => removeFromCartHandler(item) but at any other point assume the payload is id. You probably want to do () => removeFromCartHandler(item.id).
I'm using useReducer and useContext as a flux store and i'm boggled on why i'm unable to update the stores state. Here is a sample of the code I'm working with.
reducer and initial state
export const localState = {
modal_open: false,
date: null
}
export const reducer = (state , action) => {
switch (action.type) {
case "OPEN_MODAL":
return {
...state,
modal_open: true
}
case "CLOSE_MODAL":
return {
...state,
modal_open: false
}
case "SET_DATE":
return {
...state,
date: action.payload
}
default:
return state;
}
};
context constructor and component wrapping
import React from "react";
const LocalWebSiteContext= React.createContext();
const WebsiteDetails = () => {
const [state, dispatch] = React.useReducer(reducer, localState)
const [averPositions, setAverPositions] = React.useState()
const classes = useStyles();
let match = useRouteMatch("/app/websites/:id/:slug");
let { id } = useParams();
const context = useContext(UserContext);
const history = useHistory();
//testing
const { localContext } = context
const websiteData = localContext.websites.find((el, idx) => {
if(el.website_id === id){
return el;
}
});
const userAndWebsiteData = {
...websiteData,
...localContext.user
}
if(!userAndWebsiteData){
console.log('Not a valid website');
history.push('/app/websites/');
// return;
}
return (
<>
<localWebSiteContext.Provider value={{state, dispatch}}>
{ userAndWebsiteData &&
<Page className={classes.root} title="Analytics Dashboard">
{/* { JSON.stringify(userAndWebsiteData) }
{ id } */}
<Header websiteData={userAndWebsiteData} />
<Grid className={classes.container} container spacing={3}>
<Grid item xs={12}>
<Overview websiteData={userAndWebsiteData} />
</Grid>
<Grid item lg={8} xl={9} xs={12}>
<CustomModal />
<FinancialStats websiteData={userAndWebsiteData}/>
</Grid>
<Grid item lg={4} xl={3} xs={12}>
{/* {
JSON.stringify(userAndWebsiteData.positionRangesData)}
*/}
<Top5To30 positionData={userAndWebsiteData.positionRangesData
? userAndWebsiteData.positionRangesData : {} } />
range bar chart
<EarningsSegmentation />
</Grid>
<Grid item lg={12} xs={12}>
<KeywordsList />
</Grid>
<Grid item lg={4} xs={12}>
<CustomerActivity />
</Grid>
</Grid>
</Page>
}
</localWebSiteContext.Provider>
</>
);
};
Component where i'm dispatching the payload to change date
const FinancialStats = props => {
const {state, dispatch} = React.useContext(localWebSiteContext)
const { websiteData, className, handleAverData, ...rest } = props;
const [dateId, setDateId] = React.useState(null)
const [disabled, setDisabled] = useState([]);
const handleModalOpen = () => {
const averPositions = websiteData.dailyAveragePositions;
if (averPositions){
const dateNum = parseInt(averPositions[0].d)
dispatch({type: 'OPEN_MODAL', payload: dateNum})
}
}
return (
<span
key={dataKey}
className="legend-item"
style={style}
>
<Surface width={10} height={10}>
<Symbols cx={5} cy={5} type="circle" size={50} fill={color} />
{active && (
<Symbols
cx={5}
cy={5}
type="circle"
size={25}
fill={'#FFF'}
/>
)}
</Surface>
<span>{dataKey}</span>
</span>
);
})}
</div>
);
};
The expected behavior is when I dispatch({type: 'SET_DATE', payload: dateNum) it will reflect that all other components subscribed to the useContext hook. But i cannot for the life of me get it to update from null... I'm able to switch the bool values. Even when I tried to set it locally in a useState hook it's still null? Maybe has something to do with renders? Thanks in advance!!
On my follow up question from here : How to pass data from child to parent component using react hooks
I have another issue.
Below is the component structure
export const Parent: React.FC<Props> = (props) => {
const [disabled, setDisabled] = React.useState(false);
const createContent = (): JSX.Element => {
return (
<Authorization>
{<ErrorPanel message={errorMessage} setDisabled={setDisabled}/>}
<MyChildComponent/>
</<Authorization>
);
}
return (
<Button onClick={onSubmit} disabled={disabled}>My Button</Button>
{createContent()}
);
};
const Authorization: React.FC<Props> = (props) => {
const [disabled, setDisabled] = React.useState(false);
const render = (errorMessage : JSX.Element): JSX.Element => {
return (
<>
{<ErrorPanel message={errorMessage} setDisabled={setDisabled}/>}
</>
);
};
return (
<>
<PageLoader
queryResult={apiQuery}
renderPage={render}
/>
{props.children}
</>
);
};
How do I pass the disabled state value from Authorization component to my child which is invoked by
{props.children}
I tried React.cloneElement & React.createContext but I'm not able to get the value disabled to the MyChildComponent. I could see the value for disabled as true once the errorMessage is set through the ErrorPanel in the Authorization component.
Do I need to have React.useEffect in the Authorization Component?
What am I missing here?
You need to use React.Children API with React.cloneElement:
const Authorization = ({ children }) => {
const [disabled, setDisabled] = React.useState(false);
const render = (errorMessage) => {
return (
<>{<ErrorPanel message={errorMessage} setDisabled={setDisabled} />}</>
);
};
return (
<>
<PageLoader queryResult={apiQuery} renderPage={render} />
{React.Children.map(children, (child) =>
React.cloneElement(child, { disabled })
)}
</>
);
};
// |
// v
// It will inject `disabled` prop to every component's child:
<>
<ErrorPanel
disabled={disabled}
message={errorMessage}
setDisabled={setDisabled}
/>
<MyChildComponent disabled={disabled} />
</>
You can make use of React.cloneElement to React.Children.map to pass on the disabled prop to the immediate children components
const Authorization: React.FC<Props> = (props) => {
const [disabled, setDisabled] = React.useState(false);
const render = (errorMessage : JSX.Element): JSX.Element => {
return (
<>
{<ErrorPanel message={errorMessage} setDisabled={setDisabled}/>}
</>
);
};
return (
<>
<PageLoader
queryResult={apiQuery}
renderPage={render}
/>
{React.Children.map(props.children, child => {
return React.cloneElement(child, { disabled })
})}
</>
);
};
UPDATE:
Since you wish to update the parent state to, you should store the state and parent and update it there itself, instead of storing the state in child component too.
export const Parent: React.FC<Props> = (props) => {
const [disabled, setDisabled] = React.useState(false);
const createContent = (): JSX.Element => {
return (
<Authorization setDisabled={setDisabled}>
{<ErrorPanel message={errorMessage} disabled={disabled} setDisabled={setDisabled}/>}
<MyChildComponent disabled={disabled}/>
</<Authorization>
);
}
return (
<Button onClick={onSubmit} disabled={disabled}>My Button</Button>
{createContent()}
);
};
const Authorization: React.FC<Props> = (props) => {
const render = (errorMessage : JSX.Element): JSX.Element => {
return (
<>
{<ErrorPanel message={errorMessage} disabled={props.disabled} setDisabled={props.setDisabled}/>}
</>
);
};
return (
<>
<PageLoader
queryResult={apiQuery}
renderPage={render}
/>
{props.children}
</>
);
};