Add product with Attributes to cart using redux - reactjs

I need help in Reactjs app 'Add to cart'. I am using redux and to add products to cart with quantity and everything going right until tried to add specific attributes such as size or color and I don't know how to handle this can anyone explain me the logic to do it?
this is the reducer
case actionTypes.ADD_TO_CART:
const item = state.data.find((item) => item.id === action.payload.id);
const inCart = state.cart.find((item) =>
item.id === action.payload.id ? true : false
);
return {
...state,
cart: inCart
? state.cart.map((item) =>
item.id === action.payload.id
? {
...item,
qty: item.qty + 1,
selectedAttributes: [...item.selectedAttributes]
}
: item
)
: [...state.cart, {
...item, qty: 1,
selectedAttributes: []
}],
};
case actionTypes.ADJUST_ATTRIBUTES:
let selectedAttributes = [];
return {
...state,
cart: state.data.map((item) =>
item.id === action.id
? {
...item,
selectedAttributes: { [action.selectedAttributes.name]: action.selectedAttributes.value }
}
: item
),
};
this is action
export const addToCart = (itemID) => {
return {
type: actionTypes.ADD_TO_CART,
payload: {
id: itemID
}
}
}
export const selectAttribute = (itemID, name, value) => {
return {
type: actionTypes.ADJUST_ATTRIBUTES,
id: itemID,
selectedAttributes: {
name: name,
value: value
}
}
}
this is the radio button I want to select from it to add attribute
attribute.items.map((item, index2) => {
return (
<div className="button" key={index2}>
<input
onClick={() =>
this.props.selectAttribute(
this.props.currentItem.id,
attribute.name,
item.value
)
}
type="radio"
disabled={!this.props.currentItem.inStock}
name={`${this.props.currentItem.name}-${attribute.name}-${index}`}
value={item.value}
className="attributes-value"
/>
<label
className="attributes-label"
htmlFor={`${this.props.currentItem.name}-${attribute.name}-${index}`}
>
{item.value}
</label>
</div>
);
})

Related

Iam making a Todo using (useReducer and useContext) but the update functionality is not working please check this out

This is my App.jsx , where iam displaying all the todos, after clicking edit the input gets the value of selected todo and while changing the value and clicking on update it does nothing.
export default function App() {
const { dispatch, state } = useContext(todoContext);
const [todo, setTodo] = useState("");
const [isEditing, setIsEditing] = useState(false);
const [selectedTodo, setSelectedTodo] = useState("");
const handleSubmit = (e) => {
const id = uuidv4();
e.preventDefault();
if (!isEditing) {
dispatch({
type: "ADD_TODO",
payload: { id: id, text: todo },
});
setTodo("");
} else {
dispatch({
type: "UPDATE_TODO",
payload: selectedTodo,
});
setIsEditing(false);
setTodo("");
}
};
const handleEdit = (val) => {
setIsEditing(true);
const item = state.todos.find((todo) => todo.id === val.id);
setTodo(item.text);
setSelectedTodo(item);
};
return (
<div className="App">
<br />
<h1>Hello World This is Todo App</h1>
<br />
<form onSubmit={handleSubmit}>
<input
value={todo}
onChange={(e) => setTodo(e.target.value)}
type="text"
placeholder="Enter a todo"
/>
<button>{isEditing ? "Update" : "Submit"}</button>
</form>
{state.todos.map((item) => (
<div key={item.id} className="todoitem">
<p
onClick={() => dispatch({ type: "TOGGLE_TODO", payload: item.id })}
style={{
cursor: "pointer",
textDecoration: item.completed ? "line-through" : "",
}}
>
{item.text}
</p>
<span
onClick={() => dispatch({ type: "REMOVE_TODO", payload: item.id })}
>
×
</span>
<button onClick={() => handleEdit(item)}>Edit</button>
</div>
))}
</div>
);
}
This is my reducers (todoReducer.jsx) , where the upadte functionality doesnot work when i click edit to change the value and click update it does nothing
export const INITIAL_STATE = {
todos: [],
updated: [],
};
export const reducer = (state, action) => {
switch (action.type) {
case "ADD_TODO":
return {
...state,
todos: [...state.todos, action.payload],
};
case "REMOVE_TODO": {
return {
...state,
todos: state.todos.filter((item) => item.id !== action.payload),
};
}
case "TOGGLE_TODO":
return {
...state,
todos: state.todos.map((todo) =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
),
};
case "UPDATE_TODO":
return {
...state,
todos: state.todos.map((todo) => {
if (todo.id === action.payload.id) {
return action.payload;
}
return todo;
}),
};
default:
return state;
}
};
you need to update text with latest text
selectedTodo has old data
todo have new text
in your handleSubmit please update below code
dispatch({
type: "UPDATE_TODO",
payload: { ...selectedTodo, text: todo },
});

How to handle old state reducer react

I am creating a cart where the client has multiple checkboxes, I can update the cart when the checkbox is checked and update it when is unchecked, so far so good, but the problem is when the client wants to create another order, I think the reducer is bringing me the old order.
This is my reducer
if (action.type === ADD_ITEM_TO_CART_DETAILS) {
return {
...state,
cart: [...state.cart, action.payload]
}
}
This is my action
export function addItemDetail(value){
return {
type : ADD_ITEM_TO_CART_DETAILS,
payload: value
}
}
and this is the JS:
export default function DetailProduct() {
const dispatch = useDispatch();
const { id } = useParams();
const history = useHistory();
useEffect(() => {
dispatch(getDetail(id));
}, [dispatch, id]);
const detail = useSelector((state) => state.detail); // details of the products
const { options, setOptions } = useContext(OrderContext);
const BackToProducts = () => {
if (options.salsa.length) {
dispatch(addItemDetail(options));
} else {
history.push("/productos");
}
};
const seeCart = () => {
if (options.salsa.length) {
dispatch(addItemDetail(options));
} else {
history.push("/carrito");
}
};
const handleComments = (e) => {
setOptions((prev) => ({
...prev,
Comments: e.target.value,
}));
};
const handleSalsa = (e) => {
const { name, checked } = e.target;
if (options.salsa.length <= 2) {
e.target.checked = false;
} else if (checked === true) {
setOptions((prev) => ({
...prev,
salsa: [...prev.salsa, name],
picture_url: detail.picture_url,
id: uuidv4(),
price: detail.price,
title: detail.title,
}));
}
if (checked === false) {
setOptions((prev) => ({
...prev,
salsa: prev.salsa.filter((p) => p !== name),
}));
}
};
const handleToppings = async (e) => {
const { name, checked } = e.target;
if (checked === true) {
setOptions({ ...options, toppings: [...options.toppings, name] });
}
if (checked === false) {
setOptions((prev) => ({
...prev,
toppings: prev.toppings.filter((p) => p !== name),
}));
}
};
useEffect(() => {
// useEffect to update the total amount
const productPrice = options.price; // price of the single product
const toppingPrice = options.priceTopping; // price of the topping
const total = toppingPrice ? productPrice + toppingPrice : productPrice;
setOptions((prev) => ({ ...prev, unit_price: total })); // set total amount product plus toppings
}, [options.price, options.priceTopping, setOptions]);
useEffect(() => {
// useEffect to update total amount of the toppings
const numberOfToppings = options.toppings.length;
const totalPriceTopping = numberOfToppings !== 0 ? numberOfToppings * 119 : 0;
setOptions((prev) => ({ ...prev, priceTopping: totalPriceTopping }));
}, [options.toppings, setOptions]);
return (
<MainContainer>
{detail.picture_url ? <PhotoProduct src={`https://hit-pasta.herokuapp.com/${detail.picture_url}`} /> : <Loading />}
<Like
onClick={() => {
history.push("/productos");
}}
/>
<ContainerOption>
{detail &&
detail?.salsas?.map((p, index) => {
return (
<ContainerOptionChild key={index}>
<div>
<Drop />
<LabelProductName>{p.sauce}</LabelProductName>
</div>
<InputOptions type="checkbox" checked={options.salsa.index} key={index} name={p.sauce} value={p.sauce} onChange={handleSalsa} />
<Description>{p.description}</Description>
</ContainerOptionChild>
);
})}
</ContainerOption>
<MainBoxComments>
<h3>Comentarios</h3>
<BoxComentario type="text" value={options.Comments} onChange={handleComments} placeholder="Agrega instrucciones o comentarios a tu orden" />
</MainBoxComments>
<MainBoxBtns>
<Okay onClick={seeCart}>
OKAY <CartIcon />
</Okay>
<BtnArmarOtroHit onClick={BackToProducts}>ARMAR OTRO HIT</BtnArmarOtroHit>
</MainBoxBtns>{" "}
</MainContainer>
);
}

useReducer incrementing / decrementing 2 instead of 1

I'm just trying to learn the useReducer hook in react. Just playing around. Can someone help with an issue I'm having? When I click the button to "plus a unit" or "minus" a unit it takes 2 away. Any suggestions?
Perhaps map. is a bad method for updating the state in the reducer?
Test code below. I've put it all into one component for purpose of below, rather than posting all the context stuff and children.
Any help much appreciated :)
import React, {useReducer} from 'react'
function Test() {
const itemFile = [
{sku: '123456', description: 'item 1', stockavailable: 5},
{sku: '654321', description: 'item 2', stockavailable: 1},
{sku: '666666', description: 'item 3', stockavailable: 0},
]
const reducer = (state,action) => {
switch (action.type) {
case 'DELETE_LINE':
let newstate = state.filter(item => item.sku !== action.payload)
return newstate
case 'MINUS_ONE_UNIT':
return state.map(item => {
if(item.sku === action.payload) {
item.stockavailable = item.stockavailable-1;
}
return item;
})
case 'PLUS_ONE_UNIT':
return state.map(item => {
if(item.sku === action.payload) {
item.stockavailable = item.stockavailable+1;
}
return item;
})
default:
return state
}
}
const [liveItemFile, dispatch] = useReducer(reducer, itemFile);
return (
<>
{liveItemFile.map((item) => (
<div key={item.sku}>
{item.sku}: {item.description} - {item.stockavailable}
<button onClick={() => dispatch({type: 'DELETE_LINE', payload: item.sku})}>
DELETE LINE
</button>
<button onClick={() => dispatch({type: 'MINUS_ONE_UNIT', payload: item.sku})}>
Minus Unit
</button>
<button onClick={() => dispatch({type: 'PLUS_ONE_UNIT', payload: item.sku})}>
Plus Unit
</button>
</div>
))}
</>
);
}
export default Test;
Move your reducer's logic and your initial state outside of your component as with each render they get evaluated:
import React, { useReducer } from "react";
const itemFile = [
{ sku: "123456", description: "item 1", stockavailable: 5 },
{ sku: "654321", description: "item 2", stockavailable: 1 },
{ sku: "666666", description: "item 3", stockavailable: 0 },
];
const reducer = (state, action) => {
switch (action.type) {
case "DELETE_LINE":
let newstate = state.filter((item) => item.sku !== action.payload);
return newstate;
case "MINUS_ONE_UNIT":
return state.map((item) => {
if (item.sku === action.payload) {
item.stockavailable = item.stockavailable - 1;
}
return item;
});
case "PLUS_ONE_UNIT":
return state.map((item) => {
if (item.sku === action.payload) {
item.stockavailable = item.stockavailable + 1;
}
return item;
});
default:
return state;
}
};
function Test() {
const [liveItemFile, dispatch] = useReducer(reducer, itemFile);
return (
<>
{liveItemFile.map((item) => (
<div key={item.sku}>
{item.sku}: {item.description} - {item.stockavailable}
<button onClick={() => dispatch({ type: "DELETE_LINE", payload: item.sku })}>DELETE LINE</button>
<button onClick={() => dispatch({ type: "MINUS_ONE_UNIT", payload: item.sku })}>Minus Unit</button>
<button onClick={() => dispatch({ type: "PLUS_ONE_UNIT", payload: item.sku })}>Plus Unit</button>
</div>
))}
</>
);
}
export default Test;

ReactJS Render Table for each value on Array

I have a MultiSelect and a React Table..
the Select stores the values into value Array..
The way it is now i´m able to select ONE option and the table displays the data correctly. But, i´m looking to render a table for each selected option. How could i achieve something like this?
handleSelectChange (value) {
console.log('You\'ve selected:', value);
this.setState({ value: value }, () => this.fetchTable());
}
fetchTable() {
const url = 'http://localhost:8000/issues/from/';
const value = this.state.value;
const string = url+value;
fetch(string)
.then(function(response) {
return response.json();
})
.then((myJson) => this.setState({data: myJson.issues}));
}
componentDidMount() {
this.fetchData();
}
render() {
const filteredResult = this.state.boards.map(item => (
{
value: item.key,
label: item.name,
}
));
const filteredResult1 = this.state.data.map(item => (
{
name: item.fields,
id: item.id,
key: item.key,
}
));
return (
<div>
<Select
closeOnSelect={!stayOpen}
disabled={disabled}
multi
onChange={this.handleSelectChange}
options={filteredResult}
placeholder="Select Assignee(s)..."
removeSelected={this.state.removeSelected}
rtl={this.state.rtl}
simpleValue
value={value}
/>
<ResponseTable data={filteredResult1} />
</div>
);
}
}
How does your ResponseTable component look like? I guess you can just use the map function to loop and display the table rows. Sth like this:
const data = [{name: 'Test 1', id: 1, key: 'key_1'}, {name: 'Test 2', id: 2, key: 'key_2'}, {name: 'Test 3', id: 3, key: 'key_3'}];
_renderTableBody = () => {
return data.map((item) => (
return (
<TableRow>
<TableCell>item.name</TableCell>
<TableCell>item.id</TableCell>
<TableCell>item.key</TableCell>
</TableRow>
)
))
}
Then inside your render function, you can just replace this
<ResponseTable data={filteredResult1} />
into the code like this:
{this._renderTableHead()} // same method as _renderTableBody() to generate the table head title
{this._renderTableBody()}
Hope this can help!
Just keep some dummy key in state which as empty array initially. It will push the selected value of select option in to it. like below
constructor(props){
this.state = {
selectedValues: []
}
}
Alter your handleSelectChange like below. It needs to update the current selected value in this array
handleSelectChange (value) {
console.log('You\'ve selected:', value);
//this.setState({ value: value }, () => this.fetchTable());
let currentSelectedValue = this.state.selectedValues.filter(selected => selected == value)
//it will return a value if it is found otherwise empty array will be returned
if(currentSelectedValue.length == 0){
let updatedSelectedValue = this.state.selectedValues.push(value)
this.setState({ selectedValues: updatedSelectedValues }, () => this.fetchTable());
}
}
removeSelected (value) {
console.log('You\'ve selected:', value);
//this.setState({ value: value }, () => this.fetchTable());
let currentSelectedValue = this.state.selectedValues.filter(selected => selected !== value) //this will delete the removed option from array
this.setState({ selectedValues: currentSelectedValue }, () => this.fetchTable());
}
fetchTable() {
if( this.state.selectedValues.length > 0 ){
this.state.selectedValues.map((value)=>{
const url = 'http://localhost:8000/issues/from/';
const string = url+value;
fetch(string)
.then(function(response) {
return response.json();
})
.then((myJson) => this.setState({data: [...this.state.data, myJson.issues]})); //use spread operator to combine the result of each selectedValue
});
}
}
render() {
const filteredResult = this.state.boards.map(item => (
{
value: item.key,
label: item.name,
}
));
const filteredResult1 = this.state.data.map(item => (
{
name: item.fields,
id: item.id,
key: item.key,
}
));
return (
<div>
<Select
closeOnSelect={!stayOpen}
disabled={disabled}
multi
onChange={this.handleSelectChange}
options={filteredResult}
placeholder="Select Assignee(s)..."
removeSelected={this.state.removeSelected}
rtl={this.state.rtl}
simpleValue
value={value}
/>
this.state.data.map((item) => { // item here will hold the json object of { id: item.id, key: item.key, name: item.fields }
<ResponseTable data={item} />
})
</div>
);
}
}

Edit action in react-redux

I am trying to perform an edit action in react-redux. First, I created a button on the index page.
<Link to = {`/standups/${list.id}`}>Edit</Link>
When I clicked on this button, it went to the edit page but no data was present.
In my edit page, I have this code:
class StandupEdit extends Component {
constructor(props){
super(props);
this.state = {
standups: {
user_id: this.props.standup ? this.props.standup.user_id : '',
day: this.props.standup ? this.props.standup.day : '',
work_done: this.props.standup ? this.props.standup.work_done : '',
work_planned: this.props.standup ? this.props.standup.work_planned : '',
blocker: this.props.standup ? this.props.standup.blocker : ''
},
submitted: false
};
}
handleSubmit = (event) => {
event.preventDefault();
const { standups } = this.state;
const { dispatch } = this.props;
if(standups.work_done && standups.work_planned && standups.blocker) {
dispatch(addStandup(this.state.standups))
} else {
this.setState({
submitted: true
})
}
}
componentWillReceiveProps = (nextProps) => {
debugger
this.setState({
standups: {
user_id: nextProps.user_id,
day: nextProps.day,
work_done: nextProps.work_done,
work_planned: nextProps.work_planned,
blocker: nextProps.blocker
}
});
}
componentDidMount(){
if(this.props.match.params.id){
this.props.editStandup(this.props.match.params.id)
}
}
handleChange = (event) => {
const {name, value} = event.target;
const {standups} = this.state;
this.setState({
standups: {
...standups,
[name]: value
}
});
}
render() {
const {standups, submitted, fireRedirect} = this.state
return (
<MuiThemeProvider theme={theme}>
<Paper>
<h1 className='header'>Add new standup</h1>
</Paper>
<Grid container spacing={16}>
<form onSubmit={this.handleSubmit}>
<Paper className='form'>
<TextField fullWidth name= "work_done"
value = {standups.work_done}
onChange= {this.handleChange}
type= "text"
placeholder= "What did you work on yesterday?"/>
{
submitted && !standups.work_done ?
<p>Work done is required</p> : ''
}
</Paper>
<Paper className='form'>
<TextField fullWidth name= "work_planned"
value = {standups.work_planned}
onChange= {this.handleChange}
type= "text"
placeholder= "What are you planning to work on today?"/>
{
submitted && !standups.work_planned ?
<p>Work planned is required</p> : ''
}
</Paper>
<Paper className='form'>
<TextField fullWidth name= "blocker"
value = {standups.blocker}
onChange= {this.handleChange}
type= "text"
placeholder= "Any impediments in your way?"/>
{
submitted && !standups.blocker ?
<p>Blocker required</p> : ''
}
</Paper>
<div className='button'>
<Button type="submit">Update</Button>
</div>
</form>
</Grid>
</MuiThemeProvider>
);
}
}
function mapStateToProps(state, props){
if (props.match.params.id) {
return {
standup: state.standup.standups.findIndex(item => item.id === props.match.params.id)
}
}
return {
standup: null
}
}
export default connect(mapStateToProps, {editStandup})(StandupEdit);
In action, I have this code:
export function editStandup(id) {
return dispatch => {
axios(`${ROOT_URL}/standups/${id}/${API_KEY}`, {
headers: authHeader(),
method: 'GET',
}).then( response => {
dispatch(success(response.data.standup))
})
}
function success(response) {
return {
type: 'STANDUP_EDIT',
payload: response
}
}
}
export function updateStandup(standup) {
const request = axios({
headers: authHeader(),
method: 'PUT',
url: `${ROOT_URL}/standups${API_KEY}`,
data: standup
})
return {
type: 'STANDUP_UPDATE',
payload: request
}
}
And I have following code in my reducer:
export function standup(state = {}, action){
switch (action.type) {
case 'STANDUP_UPDATE':
return state.map(item => {
if(item.id === action.payload.id)
return
standup: action.payload
return item;
});
case 'STANDUP_EDIT':
const index = state.standups.findIndex(item => item.id === action.payload.id);
if (index > -1){
return state.standups.map(item => {
if(item.id === action.payload.id)
return action.payload
});
}else{
return
standup: action.payload
}
default:
return state;
}
}
In my reducer findIndex(item => item.id === action.payload.id);, item => item.id contain error item.id is undefined.
Will anyone help me in solving this problem?
You aren't updating the state in reducer correctly, standups is not defined initially which you must do. Check the same code below
export function standup(state = {standups: []}, action){
switch (action.type) {
case 'STANDUP_UPDATE': {
const index = state.standups.findIndex(item => item.id === action.payload.id);
return {
...state,
standups: {
...state.standups.slice(0, index),
action.payload,
...state.standups.slice(index + 1),
}
}
}
case 'STANDUP_EDIT':
const index = state.standups.findIndex(item => item.id === action.payload.id);
if (index > -1){
return {
...state,
standups: {
...state.standups.slice(0, index),
action.payload,
...state.standups.slice(index + 1),
}
}
}
return {
...state,
standups: {
...state.standups
action.payload,
}
default:
return state;
}
}
Also your component name is StandupEdit and you are connecting StandupNew with the connect function
export default connect(mapStateToProps, {editStandup})(StandupEdit);

Resources