onClick function not working using context-api - javascript

The handleDetail() function for clicking a product and pulling up the product detail page isn't working. My addToCart function is working properly so I don't know what I'm missing.
context.js:
state = {
products: [],
productDataDetail: productDataDetail,
};
getItem = (id) => {
const product = this.state.products.find((item) => item.id === id);
return product;
};
handleDetails = id => {
const product = this.getItem(id);
this.setState(() => {
return { productDataDetail: product };
});
};
render() {
return (
<ProductContext.Provider
value={{
...this.state,
handleDetails: this.handleDetails,
}}
>
{this.props.children}
</ProductContext.Provider>
);
}
product.js:
<ProductConsumer>
{(value) => (
<Card className={classes.root}>
<CardActionArea onClick={() => value.handleDetails(id)}>
)}
</ProductConsumer>
Product Data:
export const productDataDetail = {
id: 0,
name: "Desk",
img: desk,
store: "Local Furniture Shop 1",
price: 9.99,
desc:
"This sturdy desk is built to outlast years of coffee and hard work. You get a generous work surface and a clever solution to keep cords in place underneath.",
inCart: false,
count: 0,
total: 0,
};

bind it:
value={{
...this.state,
handleDetails: this.handleDetails.bind(this)
}}
Otherwise, when you call value.handleDetails, the function is bound to a different context (value) than the one you intended (the component's context)

Related

react-beautiful-dnd: Prevent flicker when drag and drop a lists

I'm using this react-beautiful-dnd library to be able to reorder lists. However, even though I'm able to drag and drop and re-order, there is a flicker when I try to reorder lists.
You can see in the video:
enter image description here
Here is my code:
I added sorting to the array by order before mapping.
const Board = () => {
const [currentId, setCurrentId] = useState(null);
const lists = useSelector((state) => state.lists);
const dispatch = useDispatch();
const classes = useStyles();
useEffect(() => {
dispatch(getLists());
}, [currentId, dispatch]);
const onDragEnd = (result) => {
const { destination, source, draggableId, type } = result;
if(!destination) return;
const droppableIdStart = source.droppableId;
const droppableIdEnd = destination.droppableId;
const droppableIndexStart = source.index;
const droppableIndexEnd = destination.index;
const newState = [...lists];
// drag lists
if(type === 'list') {
const dragList = newState.splice(droppableIndexStart, 1);
newState.splice(droppableIndexEnd, 0, ...dragList);
// update order list to be index
newState.forEach((list, index) => {
list.order = index;
dispatch(updateList(list._id , { ...list }));
});
}
return newState;
}
// Arranging lists by order
const newArrange = (a,b) => {
return (a.order - b.order);
}
lists.sort(newArrange);
return (
<>
<DragDropContext onDragEnd={onDragEnd} >
<div>
<h1>Board</h1>
<Droppable droppableId="all-lists" direction="horizontal" type="list">
{ provided => (
<div className={classes.listContainer} {...provided.droppableProps} ref={provided.innerRef} >
{ lists.map((list, index) =>
(user?.result?.googleId === list?.creator || user?.result?._id === list?.creator) ?
<List key={list._id} title={list.title} cards={list.cards} currentId={list._id} index={index} /> :
null
)}
{addListFlag && (
<InputItem
value={listData.title}
btnText={"Add list"}
type={"list"}
placeholder={"Enter a list title..."}
changedHandler={handleChange}
closeHandler={closeHandlerBtn}
addItem={submitHandler}
/>
)}
{!addListFlag && (
<AddBtn btnText={"Add another list"} type={"list"} handleClick={handleAddition} />
)}
{provided.placeholder}
</div>
)}
</Droppable>
</div>
</DragDropContext>
</>
)
}
export default Board;
Sample of data:
{
_id: "6163cdd306d27b",
title: "a",
name: "first one",
order: "0",
cards:[
{
id: "0",
text: "a1",
_id: {_id: "61d0ca2c20d27e"}
},
{
id: "1",
text: "a2",
_id: {_id: "616541ec90630"}
},
{
id: "2",
text: "a3",
_id: {_id: "61606e609"}
}
]
}
Thank :)
It has probably todo with your getLists() call in the useEffect hook.
Is there an async function behind it? Do you get your lists from a server? If so, I suppose that useEffect is firing twice (once on drag end and once when it gets back the data from the backend or whatever getLists is doing), which leads to the flickering.
It would probably help to know what exactly getLists is.

How to select and save my components ids in an array?

I have a landing Page with an array that is passed to my TemplateList component. And my TemplateList component is made up by TemplateCard components.
Each card on my TemplateList can be selected, and if more than 2 of those cards are selected, the button on the page activates onOnboardingComplete, and it continues to the next page.
What I would like to do, is to be able to grab each card's unique Id that I selected on the page And once I select one or more, convert those ids into an array (called: selectedTemplatesIds) that I can pass to onOnboardingComplete (the button).
Using the id as an identifier, and based on that we are getting an array of selected cards and passing it with onOnboardingComplete.
I hear I can use Object.keys, but never done this before. How can I do this?
Summary:
1. I want to be able to grab my card unique ids when selected and ignore them when unselected
2. Once I select one or more, convert those ids into an array (selectedTemplatesIds) in order to pass them to onOnboardingComplete (the button in TemplateList).
Here is the array I use in my LandingPage
templates = [
{
title: "Grocery List",
description: "Description of what this things does so the reader can have info of",
imgURL: "https://res.cloudinary.com/deruwllkv/image/upload/v1625753993/groceries.png",
id: 0,
},
{
title: "Shopping Space",
description: "blablabalblabla",
imgURL: "https://res.cloudinary.com/deruwllkv/image/upload/v1625753766/shopping.png",
id: 1,
},
{
title: "Travel Planning",
description: "blablabalblabla",
imgURL: "https://res.cloudinary.com/deruwllkv/image/upload/v1625753885/travel.png",
id: 2,
},
{
title: "Travel",
description: "blablabalblabla",
imgURL: "https://res.cloudinary.com/deruwllkv/image/upload/v1625753993/groceries.png",
id: 3,
},
];
My Templateslist component:
export type Template = {
title: string;
description: string;
imgURL: string;
id?: number;
};
type Props = {
templates: Template[];
onOnboardingComplete: Function;
};
const TemplateList = ({ templates, onOnboardingComplete }: Props) => {
const { aspectRatio, vmin } = useWindowResponsiveValues();
const [noOfSelectedCards, setNoOfSelectedCards] = useState(0);
const handleSelect = () => setNoOfSelectedCards(noOfSelectedCards + 1);
const handleDeselect = () => setNoOfSelectedCards(noOfSelectedCards - 1);
let buttonText;
if (noOfSelectedCards === 1) {
buttonText = "Select at least 1 more option";
} else if (noOfSelectedCards >= 2) {
buttonText = "Create my initial cuadds!";
} else {
buttonText = "Select at least 2 options";
}
return (
<>
<div className={styles.scrollContainer}>
{templates.map((item) => (
<TemplateCard
title={item.title}
description={item.description}
img={item.imgURL}
classNameToAdd={styles.cardContainer}
key={item.id}
onSelectCard={handleSelect}
onDeselectCard={handleDeselect}
/>
))}
</div>
<MenuButton
onClick={onOnboardingComplete}
style={actionButton}
className={
noOfSelectedCards >= 2 ? `${styles.actionBlue}` : `${styles.actionNormal}`
}
>
{buttonText}
</MenuButton>
</>
);
};
export default TemplateList;
And my TemplateCards:
type Props = {
title: string;
description: string;
img: string;
classNameToAdd?: string;
classNameOnSelected?: string;
onSelectCard: any;
onDeselectCard: any;
};
const TemplateCard = ({
title,
description,
img,
classNameToAdd,
classNameOnSelected,
onSelectCard,
onDeselectCard,
}: Props) => {
const { aspectRatio, vmin } = useWindowResponsiveValues();
let className = `${styles.card} ${classNameToAdd}`;
const [selected, setSelected] = useState(false);
const handleClick = () => {
setSelected(!selected);
if (selected) {
onDeselectCard();
} else {
onSelectCard();
}
};
if (selected) {
className += `${styles.card} ${classNameToAdd} ${classNameOnSelected}`;
}
return (
<div style={card} className={className} onClick={handleClick}>
<img style={imageSize} src={img}></img>
<div style={cardTitle}>
{title}
{selected ? <BlueCheckIcon style={blueCheck} className={styles.blueCheck} /> : null}
</div>
<div style={descriptionCard}>{description}</div>
</div>
);
};
TemplateCard.defaultProps = {
classNameOnSelected: styles.selected,
};
export default TemplateCard;
Add:
const selectedTemplatesIds = [];
Change handlers:
const handleSelect = (id) => {
selectedTemplatesIds.push(id);
setNoOfSelectedCards(noOfSelectedCards + 1);
};
const handleDeselect = (id) => {
selectedTemplatesIds.splice(selectedTemplatesIds.indexOf(id),1);
setNoOfSelectedCards(noOfSelectedCards - 1);
}
Change:
<TemplateCard
title={item.title}
description={item.description}
img={item.imgURL}
classNameToAdd={styles.cardContainer}
key={item.id}
onSelectCard={() => {
handleSelect(item.id)
}}
onDeselectCard={() => {
handleDeselect(item.id)
}}
/>
Rather than just storing the noOfSelectedCards. Just store the array of ids:
const [selectedIds, setSelectedIds] = useState([]);
Send in the id into the handleSelect function and push the id into the array.
<TemplateCard
title={item.title}
description={item.description}
img={item.imgURL}
classNameToAdd={styles.cardContainer}
key={item.id}
onSelectCard={() => handleSelect(item.id)}
onDeselectCard={handleDeselect}
/>
handleSelect(id) {
setSelectedIds(prev => [...prev, id])
}
And you can do a similar thing to remove the id when deselecting a card.

React: How to Pass State on Each Mapped Array Items?

I rendered a list of buttons using Array.map method in a function component. When I tried to pass state to each mapped array items, the rendered results changed all array items at once, instead of one by one.
Here is my code. Am I doing something wrong? Sorry if the question has been solved in other thread or I used the wrong method. This is my first React project and I am still learning. It would be very appreciated if someone could advise. Thank you!
import React, { useState } from "react"
export default function Comp() {
const [isActive, setActive] = useState(false)
const clickHandler = () => {
setActive(!isActive)
console.log(isActive)
}
const data = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" },
]
const renderList = items => {
return items.map(item => (
<li key={item.id}>
<button onClick={clickHandler}>
{item.name} {isActive ? "active" : "not active"}
</button>
</li>
))
}
return (
<ul>{renderList(data)}</ul>
)
}
Put the individual item into a different component so that each has its own active state:
export default function Comp() {
const data = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" },
]
const renderList = items => (
items.map(item => <Item key={item.id} name={item.name} />)
);
return (
<ul>{renderList(data)}</ul>
)
}
const Item = ({ name }) => {
const [isActive, setActive] = useState(false);
const clickHandler = () => {
setActive(!isActive);
};
return (
<li>
<button onClick={clickHandler}>
{name} {isActive ? "active" : "not active"}
</button>
</li>
);
};
You need to set the active-id in handling the click-event. That will in-turn render active/non-active conditionally:
Notice the flow (1) > (2) > (3)
function Comp() {
const [activeId, setActiveId] = React.useState(null);
const clickHandler = (item) => {
setActiveId(item.id) // (2) click-handler will set the active id
}
const data = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" },
]
const renderList = items => {
return items.map(item => (
<li key={item.id}>
<button onClick={() => clickHandler(item)}> // (1) passing the clicked-item so that we can set the active-id
{item.name} {item.id === activeId ?
"active" : "not active" // (3) conditionally render
}
</button>
</li>
))
}
return (
<ul>{renderList(data)}</ul>
)
}
Good Luck...

React Native / Redux - rendering list of items, how to request data by id for each item to show details and not override previous requested data?

I render a list of items. On click (on each item), I am showing a details view of that item by making an api request by Id. The problem is that when I click on lets say Item1 and Item 2, the object I receive for Item2 'overrides' Item1'- meaning I always show same details data for all items, which makes sense because I re-render with the newly requested data (item2 for example). Ive been thinking and trying to find my mistake for so long now, any help would be deeply appreciated !!!!!!!!!!
my action:
export const fetchItem = (id) => {
return dispatch => {
dispatch({type: FETCH_ITEM})
return ItemAPI.getItem(id)
.then(item => {
dispatch({type: FETCH_ITEM_SUCCESS, payload: item})
})
}
}
reducer:
export default (state = initialState, action) => {
case FETCH_ITEM_SUCCESS:
return {
...state,
item: action.payload
}
}
my container:
class AppContainer extends React.Component {
componentDidMount() {
this.props.fetchItems()
}
getItem = (id) => {
this.props.fetchItem(id)
}
renderItem = ({item}) => (
<WatchedItem
itemSummary={item}
itemDetails={this.props.item}
getItem={this.getItem}
/>
)
render() {
return (
<View>
<FlatList
data={this.props.items}
renderItem={this.renderItem}
/>
</View>
)
}
}
mapStateToProps = state => {
return {
items: _.map(_.values(state.watchList.items), "item"),
item: state.watchList.item
}
}
export default connect(mapStateToProps,
{fetchItems, fetchItem})(AppContainer)
Item:
export default class WatchedItem extends React.Component {
state = {
showDetailView: false
}
getItem = () => {
this.props.getCoin(this.props.itemSummary.id)
this.setState({
showDetailView: !this.state.showDetailView
})
}
render() {
const {itemDetails, itemSummary: {name, symbol}} = this.props;
return (
<View>
<View>
<Text> {name} </Text>
// HERE IS THE PROBLEM. WHEN I CLICK ON MULTIPLE ITEMS, THE DETAILS ARE SHOWN FOR ALL BUT ONLY WITH LATEST FETCHED ITEM
<Button onPress={this.getItem}>Show Details</Button>
</View>
<View>
{this.state.showDetailView ? <WatchedItemDetailView item={itemDetails}/> : null}
</View>
</View>
)
}
}
the data looks about like this:
items: [{id:1, name: "item1"}, {id:2, name: "item2"}, ...]
item: {id:1, name: "item1", symbol: "xxx", somethingelse: "else",...}
Ps. I cannot already save all the detailsinfo of Item in "items", because that data constantly changes, so I do need to request it seperate to get current data.
Your problem is in how you're storing the items, i.e you're only saving one under the item key. Instead you could save all of them items as an object, e.g.
case FETCH_ITEM_SUCCESS:
return {
...state,
items: {
...state.items,
[action.payload.id]: action.payload
}
}
}
(it wasn't clear from your question what the state.watchList data looked like, propose changing it to just an id).
mapStateToProps = state => {
return {
items: Object.values(state.items),
item: state.items[state.watchList.id]
}
}

get component property value in reactjs

So I want to do a name generator, here's data
export const dataGenerator = {
gender: ["Male", "Female"],
region: ["France", "Germany", "Italy", "Korea", "Russia"]
}
my component :
class NameGenerator extends React.Component{
constructor(props){
super(props);
this.state={
value: 0,
name: "",
surname: "",
gender: "",
region: "",
}
this.onClick = this.onClick.bind(this);
}
// handle change
handleChange = (e, value) => {
this.setState({ value})
}
onClick(e){
if(e.keyCode === 32){
fetch('https://uinames.com/api/')
.then( res => res.json())
.then(namedata => {
this.setState({
name: namedata.name,
surname: namedata.surname
})
})
}
}
componentDidMount(){
fetch('https://uinames.com/api/')
.then( res => res.json())
.then( namedata => {
this.setState({
name: namedata.name,
surname: namedata.surname,
gender: namedata.gender,
region: namedata.region
})
});
window.addEventListener('keyup', this.onClick, false);
}
//Display Select Region
selectRegion = (value) => {
switch(value){
case 0:
return <TabContainer>{this.state.name} {this.state.surname}</TabContainer>;
case 1:
return <TabContainer>Germany</TabContainer>;
case 2:
return <TabContainer>Italy</TabContainer>;
case 3:
return <TabContainer>Korea</TabContainer>;
case 4:
return <TabContainer>Russia</TabContainer>;
default:
return 'error'
}
};
render(){
const { dataGenerator } = this.props;
const { value} = this.state;
return(
<div>
<AppBar position="static" color="default">
<Tabs
value={value}
onChange={this.handleChange}
indicatorColor="primary"
textColor="primary"
fullWidth
>
{dataGenerator.region.map( (section, i) => {
return (
<Tab key={section} value={i} label={section}/>
)
})}
</Tabs>
{this.selectRegion(value)}
<div>
</div>
</AppBar>
</div>
);
}
}
there's actually 2 problems with me
first I want to display TabContainer Component right after the Tab component without using the switch statement i tried to do this but its getting an error
{dataGenerator.region.map( (section, i) => {
return (
<Tab key={section} value={i} label={section}/>
value === {i} && <TabContainer>{section}</TabContainer>
)
that's why I'm using the switch statement but I'm looking for the alternative.
And second I want to get the property label from the Tab to use it as query in my api fetch like "https://uinames.com/api/?region=germany" so when you click its properties get add to the query in fetch api
already solved it
i just add
onClick={((e) => this.getSection(e, section))}
in
{dataGenerator.region.map( (section, i) => {
return (
<Tab key={section} value={i} label={section} onClick={((e) => this.getSection(e, section))}/>
)
some people say you shouldn't put anonymous function like that even the doc in react said it but that's what react doc do and that's the only way i can think of now, here's the getSection code
getSection = (e, section) => {
this.setState({
region: section
})
}

Categories