I am in the process of finishing my todo application, since redux is a bit new to me of course I encounter problems. My problem is when I click the delete button (recycle icon) I want a confirmation box to pop up with basic Yes and No buttons, I have built that, but... when I click one recycle button all of the other confirmation boxes get set to True and they pop up in sync. I need only one box to pop up for the right todo.id.
Note: I have built this before without redux, but I am still wrapping my head around redux.
Here is my code:
JS:
import React, {useState, Fragment} from 'react'
import { useDispatch, useSelector } from 'react-redux';
import "./todo.css"
const Todos = () => {
const dispatch = useDispatch();
const todos = useSelector(state => state.todos);
const confirmationSt = useSelector(state => state.confirmation)
const handleConfirm = id => {
dispatch({
type: "CONFIRM",
})
}
const handleContinue = () => {
dispatch({
type: "CONTINUE",
})
}
const handleClick = id => dispatch({
type: "DELETE_TODO",
payload: id,
})
if (!todos || !todos.length) {
return <p>Empty</p>
}
return (
<ul className='unlist'>{todos.map(todo =><Fragment key={todo.id}> <div className='todoContent'><li >{todo.label}</li>
<div><button className='delete' onClick={handleConfirm}><i className="fas fa-recycle"></i></button>
<button className='delete' onClick={handleConfirm}><i className="fas fa-wrench"></i></button>
</div>
</div>
{confirmationSt === true ? <div className='confirmation-box'>
Are you sure?
<button onClick={() => handleClick(todo.id)}>Yes</button>
<button onClick={handleContinue}>No</button>
</div> : null}
</Fragment>
)}
</ul>
)
}
Reducer:
const initalState = {
todos: [],
confirmation: false,
}
const reducer = (state = initalState, action) => {
switch (action.type) {
case "ADD_TODO":
return {
...state,
todos: [...state.todos, action.payload],
}
case "DELETE_TODO":
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.payload)
}
case "CONFIRM":
return {
...state,
confirmation: !state.confirmation,
}
case "CONTINUE":
return {
...state,
confirmation: false
}
default: return state;
}
}
export default reducer
I have changed confirmation from boolean to id of the task
import React, { useState, Fragment } from 'react'
import { useDispatch, useSelector } from 'react-redux';
import "./todo.css"
const Todos = () => {
const dispatch = useDispatch();
const todos = useSelector(state => state.todos);
const confirmationSt = useSelector(state => state.confirmation)
const handleConfirm = id => {
dispatch({
type: "CONFIRM",
payload: id
})
}
const handleContinue = () => {
dispatch({
type: "CONTINUE",
})
}
const handleClick = id => dispatch({
type: "DELETE_TODO",
payload: id,
})
if (!todos || !todos.length) {
return <p>Empty</p>
}
return (
<ul className='unlist'>
{ todos.map(todo =>
<Fragment key={todo.id}>
<div className='todoContent'>
<li >{todo.label}</li>
<div>
<button className='delete' onClick={()=>handleConfirm(todo.id)}>
<i className="fas fa-recycle"></i>
</button>
<button className='delete' onClick={()=>handleConfirm(todo.id)}>
<i className="fas fa-wrench"></i>
</button>
</div>
</div>
{
confirmationSt === todo.id ?
<div className='confirmation-box'>
Are you sure?
<button onClick={() => handleClick(todo.id)}>Yes</button>
<button onClick={handleContinue}>No</button>
</div>
: null
}
</Fragment>
)}
</ul>
)
}
const initalState = {
todos: [],
confirmation: -1,
}
const reducer = (state = initalState, action) => {
switch (action.type) {
case "ADD_TODO":
return {
...state,
todos: [...state.todos, action.payload],
}
case "DELETE_TODO":
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.payload)
}
case "CONFIRM":
return {
...state,
confirmation: action.payload,
}
case "CONTINUE":
return {
...state,
confirmation: false
}
default: return state;
}
}
export default reducer
Related
I am find problem in persisting data when page is refreshed. I've a file with reducer and actions. I'm using shopify-js-buy sdk for cart of my project. The cart works fine if there is not page refresh but when there is a page refresh cart item data is not persisting. I feel that if I use only open/close state is persisted but other data like subtotal , items, no.of items etc. are not persisting.
this is reducer and action file:
import { useSelector, useDispatch } from "react-redux"
import Client from "shopify-buy"
// Creates the client with Shopify-Buy and store info
//Example Storefront
const client = Client.buildClient({
storefrontAccessToken: "dd4d4dc146542ba7763305d71d1b3d38",
domain: "graphql.myshopify.com",
})
const PRODUCTS_FOUND = "shopify/PRODUCTS_FOUND"
const PRODUCT_FOUND = "shopify/PRODUCT_FOUND"
const COLLECTION_FOUND = "shopify/COLLECTION_FOUND"
const CHECKOUT_FOUND = "shopify/CHECKOUT_FOUND"
const SHOP_FOUND = "shopify/SHOP_FOUND"
const ADD_VARIANT_TO_CART = "shopify/ADD_VARIANT_TO_CART"
const UPDATE_QUANTITY_IN_CART = "shopify/UPDATE_QUANTITY_IN_CART"
const REMOVE_LINE_ITEM_IN_CART = "shopify/REMOVE_LINE_ITEM_IN_CART"
const OPEN_CART = "shopify/OPEN_CART"
const CLOSE_CART = "shopify/CLOSE_CART"
const CART_COUNT = "shopify/CART_COUNT"
const initialState = {
isCartOpen: false,
cartCount: 0,
checkout: {},
products: [],
featured: [],
product: {},
shop: {},
}
export default (state = initialState, action) => {
switch (action.type) {
case PRODUCTS_FOUND:
return { ...state, products: action.payload }
case PRODUCT_FOUND:
return { ...state, product: action.payload }
case COLLECTION_FOUND:
return { ...state, featured: action.payload }
case CHECKOUT_FOUND:
return { ...state, checkout: action.payload }
case SHOP_FOUND:
return { ...state, shop: action.payload }
case ADD_VARIANT_TO_CART:
return { ...state, checkout: action.payload }
case UPDATE_QUANTITY_IN_CART:
return { ...state, checkout: action.payload }
case REMOVE_LINE_ITEM_IN_CART:
return { ...state, checkout: action.payload }
case OPEN_CART:
return { ...state, isCartOpen: true }
case CLOSE_CART:
return { ...state, isCartOpen: false }
case CART_COUNT:
return { ...state, cartCount: action.payload }
default:
return state
}
}
// Gets all the products from Shopify
export function getProducts() {
return (dispatch) => {
client.product.fetchAll().then((resp) => {
dispatch({
type: PRODUCTS_FOUND,
payload: resp,
})
})
}
}
// Gets individual item based on id
export function getProduct(id) {
return async (dispatch) => {
const resp = await client.product.fetch(id)
dispatch({
type: PRODUCT_FOUND,
payload: resp,
})
return resp
}
}
// Creates initial checkout state from Shopify
export function checkout() {
return (dispatch) => {
client.checkout.create().then((resp) => {
dispatch({
type: CHECKOUT_FOUND,
payload: resp,
})
})
}
}
// Gets Shopify store information
export function shopInfo() {
return (dispatch) => {
client.shop.fetchInfo().then((resp) => {
dispatch({
type: SHOP_FOUND,
payload: resp,
})
})
}
}
// Adds variants to cart/checkout
export function addVariantToCart(checkoutId, lineItemsToAdd) {
return async (dispatch) => {
const response = await client.checkout.addLineItems(
checkoutId,
lineItemsToAdd
)
dispatch({
type: ADD_VARIANT_TO_CART,
payload: response,
})
return response
}
}
// Updates quantity of line items in cart and in checkout state
export function updateQuantityInCart(lineItemId, quantity, checkoutId) {
const lineItemsToUpdate = [
{ id: lineItemId, quantity: parseInt(quantity, 10) },
]
return async (dispatch) => {
const resp = await client.checkout.updateLineItems(
checkoutId,
lineItemsToUpdate
)
dispatch({
type: UPDATE_QUANTITY_IN_CART,
payload: resp,
})
return resp
}
}
// Removes line item from cart and checkout state
export function removeLineItemInCart(checkoutId, lineItemId) {
return (dispatch) => {
client.checkout.removeLineItems(checkoutId, [lineItemId]).then((resp) => {
dispatch({
type: REMOVE_LINE_ITEM_IN_CART,
payload: resp,
})
})
}
}
// To close the cart
export function handleCartClose() {
return {
type: CLOSE_CART,
}
}
// To open the cart
export function handleCartOpen() {
return {
type: OPEN_CART,
}
}
// Set the count of items in the cart
export function handleSetCount(count) {
return {
type: CART_COUNT,
payload: count,
}
}
// this is exporting reducer file as shopifyState
export { default as shopifyState } from "./Shopify" //this written in another file.
//this store.js
//===================================================================================
import { createStore, combineReducers, applyMiddleware, compose } from "redux"
import thunk from "redux-thunk";
import * as reducers from "../ReduxStore/ShopifyCart";
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const rootReducer = combineReducers(reducers)
const enhancer = composeEnhancers(applyMiddleware(thunk))
const persistConfig = {
key: 'root',
storage,
stateReconciler: autoMergeLevel2,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
export const Store = createStore(persistedReducer, undefined, enhancer)
export const PersistStore = persistStore(Store);
//this is index.js
ReactDOM.render(
<BrowserRouter>
<Provider store={Store}>
<PersistGate loading={null} persistor={PersistStore}>
<React.StrictMode>
<App />
</React.StrictMode>
</PersistGate>
</Provider>
</BrowserRouter>,
document.getElementById('root')
);
//this file contains code for showing specific product based on input from another file
import React, { useEffect, useState } from "react"
import { useDispatch, useSelector } from 'react-redux';
import { Link } from "react-router-dom"
// import { useShopify } from "../../../../hooks"
import {addVariantToCart,getProduct,handleCartOpen} from '../../../../ReduxStore/ShopifyCart/Shopify'
export default (props) => {
const dispatch = useDispatch()
const checkoutState = useSelector(
(state) => state.shopifyState.checkout
)
const product = useSelector(
(state)=>(state.shopifyState.product)
)
const id = props.match.params.productId
const defaultSize = product.variants && product.variants[0].id.toString()
const [size, setSize] = useState("")
const [quantity, setQuantity] = useState(1)
const description = product.description && product.description.split(".")
function changeSize(sizeId, quantity) {
dispatch(handleCartOpen())
if (sizeId === "") {
sizeId = defaultSize
const lineItemsToAdd = [
{ variantId: sizeId, quantity: parseInt(quantity, 10) },
]
const checkoutId = checkoutState.id
// addVariant(checkoutId, lineItemsToAdd)
dispatch(addVariantToCart(checkoutId, lineItemsToAdd))
} else {
const lineItemsToAdd = [
{ variantId: sizeId, quantity: parseInt(quantity, 10) },
]
const checkoutId = checkoutState.id
// addVariant(checkoutId, lineItemsToAdd)
dispatch(addVariantToCart(checkoutId, lineItemsToAdd))
}
}
useEffect(() => {
(dispatch(getProduct(id)))
}, [id])
return (
<div id="individualProduct">
<Link className="homeButton button" to={"/Home"}>
Home
</Link>
<div className="Product-wrapper2">
<div className="Images">
{product.images &&
product.images.map((image, i) => {
return (
<img
key={image.id + i}
src={image.src}
alt={`${product.title} product shot`}
/>
)
})}
</div>
<div className="Product__info">
<h2 className="Product__title2">{product.title}</h2>
<ul className="Product__description">
{description &&
description.map((each, i) => {
return <li key={`line-description +${i}`}>{each}</li>
})}
</ul>
<div>
<label htmlFor={"prodOptions"}>Size</label>
<select
id="prodOptions"
name={size}
onChange={(e) => {
setSize(e.target.value)
}}
>
{product.variants &&
product.variants.map((item, i) => {
return (
<option
value={item.id.toString()}
key={item.title + i}
>{`${item.title}`}</option>
)
})}
</select>
</div>
<div>
<label>Quantity</label>
<input
className="quantity"
type="number"
min={1}
value={quantity}
onChange={(e) => {
setQuantity(e.target.value)
}}
></input>
</div>
<h3 className="Product__price">
${product.variants && product.variants[0].price}
</h3>
<button
className="prodBuy button"
onClick={(e) => changeSize(size, quantity)}
>
Add to Cart
</button>
</div>
</div>
</div>
)
}
//this is cart code
import React, { useEffect } from "react"
import LineItem from "../LineItem/LineItem"
// import { useShopify } from "../../../../hooks"
import { useSelector, useDispatch } from "react-redux"
import {handleCartOpen,handleCartClose,handleSetCount} from '../../../../ReduxStore/ShopifyCart/Shopify'
// import { MdShoppingCart, MdRemoveShoppingCart } from "react-icons/md"
export default (props) => {
const cartStatus = useSelector((appState) => appState.shopifyState.isCartOpen)
const checkoutState = useSelector(
(appState) => appState.shopifyState.checkout
)
const dispatch = useDispatch()
function handleOpen(e) {
e.preventDefault()
dispatch(handleCartOpen())
}
function handleClose(e) {
e.preventDefault()
dispatch(handleCartClose())
}
function openCheckout(e) {
e.preventDefault()
// window.open(checkoutState.webUrl) // opens checkout in a new window
window.location.replace(checkoutState.webUrl) // opens checkout in same window
}
useEffect(() => {
const button = document.querySelector("button.App__view-cart")
if (cartStatus === true) {
button.classList.add("hide")
} else {
button.classList.remove("hide")
}
function getCount() {
let lineItems =
checkoutState.lineItems && checkoutState.lineItems.length > 0
? checkoutState.lineItems
: []
let count = 0
lineItems.forEach((item) => {
count += item.quantity
return count
})
dispatch(handleSetCount(count))
}
getCount()
}, [cartStatus, checkoutState])
return (
<div id="cart">
<div className={`Cart ${cartStatus ? "Cart--open" : ""}`}>
<div className="App__view-cart-wrapper2">
<button className="App__view-cart" onClick={(e) => handleOpen(e)}>
{/* <MdShoppingCart /> */}
</button>
</div>
<header className="Cart__header">
<h2>Your cart</h2>
<button className="Cart__close" onClick={(e) => handleClose(e)}>
{/* <MdRemoveShoppingCart /> */}
<span style={{fontSize: '30px'}}>X</span>
</button>
</header>
<ul className="Cart__line-items">
<LineItem />
</ul>
<footer className="Cart__footer">
<div className="Cart-info clearfix">
<div className="Cart-info__total Cart-info__small">Subtotal</div>
<div className="Cart-info__pricing">
<span className="pricing">$ {checkoutState.subtotalPrice}</span>
</div>
</div>
<div className="Cart-info clearfix">
<div className="Cart-info__total Cart-info__small">Taxes</div>
<div className="Cart-info__pricing">
<span className="pricing">$ {checkoutState.totalTax}</span>
</div>
</div>
<div className="Cart-info clearfix">
<div className="Cart-info__total Cart-info__small">Total</div>
<div className="Cart-info__pricing">
<span className="pricing">$ {checkoutState.totalPrice}</span>
</div>
</div>
<button
className="Cart__checkout button"
onClick={(e) => openCheckout(e)}
>
Checkout
</button>
</footer>
</div>
</div>
)
}
I'm working on reactJs. Will be thankfull for any help on this .
I'm trying to learn the MERN stack, and I'm going through this tutorial on YouTube. I'm getting stuck in Ep. 7. The issue I think I'm having is my ADD_ITEM action is never triggered, and so the state is never updated, and I have to reload the page in order to see any items added. The DELETE_ITEM action works properly, so I suspect there may be an issue with the ADD_ITEM action being called from a form in a modal, but I'm unsure.
Picture of my Redux DevTools after refreshing the page, deleting 2 items, and trying to add 1:
itemReducer.js
import { GET_ITEMS, ADD_ITEM, DELETE_ITEM, ITEMS_LOADING } from '../actions/types';
const initialState = {
items: [],
loading: false
};
export default function(state = initialState, action) {
console.log(action.type);
switch(action.type) {
case GET_ITEMS:
return { ...state, items: action.payload, loading: false };
case ADD_ITEM:
return { ...state, items: [action.payload, ...state] };
case DELETE_ITEM:
return { ...state,
items: state.items.filter(item => item._id !== action.payload)
};
case ITEMS_LOADING:
return {
...state,
loading: true
};
default:
return state;
}
}
ItemModal.js
import React, { Component } from 'react';
import { Button, Modal, ModalHeader, ModalBody, Form, FormGroup, Label, Input } from 'reactstrap';
import { connect } from 'react-redux';
import { addItem } from '../actions/itemActions';
class ItemModal extends Component {
state = {
modal: false,
name: ''
}
toggle = () => {
this.setState({
modal: !this.state.modal
});
}
onChange = (e) => {
this.setState({ [e.target.name]: e.target.value })
}
onSubmit = (e) => {
e.preventDefault();
const newItem = {
name: this.state.name
}
this.props.addItem(newItem);
this.toggle();
}
render() {
return(
<div>
<Button
color="dark"
style={{marginBotton: '2rem'}}
onClick={this.toggle}
>Add Item</Button>
<Modal
isOpen={this.state.modal}
toggle={this.toggle}
>
<ModalHeader
toggle={this.toggle}
>
Add To Shopping List
</ModalHeader>
<ModalBody>
<Form onSubmit={this.onSubmit}>
<FormGroup>
<Label for="item">Item</Label>
<Input
type="text"
name="name"
id="item"
placeholder="Add shopping item"
onChange={this.onChange}
/>
</FormGroup>
<Button
color="dark"
style={{marginTop: '2rem'}}
block
>Submit</Button>
</Form>
</ModalBody>
</Modal>
</div>
)
}
}
const mapStateToProps = state => ({
item: state.item
});
export default connect(mapStateToProps, { addItem })(ItemModal);
itemActions.js
import axios from 'axios';
import { GET_ITEMS, ADD_ITEM, DELETE_ITEM, ITEMS_LOADING } from './types';
export const getItems = () => dispatch => {
dispatch(setItemsLoading());
axios.get('/api/items').then(res => dispatch({
type: GET_ITEMS,
payload: res.data
}))
};
export const deleteItem = (id) => dispatch => {
axios.delete(`/api/items/${id}`).then(res => dispatch({
type: DELETE_ITEM,
payload: id
}))
};
export const addItem = (item) => dispatch => {
axios.post('/api/items', item).then(res => dispatch({
type: ADD_ITEM,
payload: res.data
}))
};
export const setItemsLoading = () => {
return {
type: ITEMS_LOADING
};
};
reducer
import { COMBINE_POST } from '../Actions/actionType'
const initialState = {
posts: null,
users: null,
comments: null,
post: null
}
export const rootReducer = (state = initialState, action) => {
switch (action.type) {
case 'getPosts':
return {
...state,
posts: action.data
}
case 'getUsers':
return {
...state,
users: action.data
}
case 'getComments':
return {
...state,
comments: action.data
}
case COMBINE_POST :
return {
...state,
post: action.payload
}
case 'removePost':
console.log('removepost', state.post.post)
return {
...state,
post: state.post.post.filter(item => item.id !==
action.payload)
}
default:
return state
}
}
action
import { COMBINE_POST } from './actionType'
export const fetchPosts = () => {
return dispatch => {
fetch(`https://jsonplaceholder.typicode.com/posts/`)
.then(res => res.json())
.then(res => dispatch({ type: 'getPosts', data: res }))
}
}
export const fetchUsers = () => {
return dispatch => {
fetch(`https://jsonplaceholder.typicode.com/users/`)
.then(res => res.json())
.then(res => dispatch({ type: 'getUsers', data: res }))
}
}
export const fetchComments = () => {
return dispatch => {
fetch(`https://jsonplaceholder.typicode.com/comments/`)
.then(res => res.json())
.then(res => dispatch({ type: 'getComments', data: res }))
}
}
export const removePost = id => {
return dispatch => {
fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, {
method: 'DELETE',
})
dispatch({ type: 'removePost', payload: id })
}
}
export const combimePost = arr => ({ type: COMBINE_POST, payload: arr })
component render
import React, { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { fetchPosts, fetchUsers, fetchComments, removePost } from '../../Redux/Actions/action'
import { combimePost } from '../../Redux/Actions/action'
import './newsList.scss'
export const NewsList = () => {
const dispatch = useDispatch()
const selector = useSelector(state => state.rootReducer)
useEffect(() => {
dispatch(
fetchPosts()
)
}, [])
useEffect(() => {
dispatch(
fetchUsers()
)
}, [])
useEffect(() => {
dispatch(
fetchComments()
)
}, [])
This is where I combine the post and user id
useEffect(() => {
const combinePost = selector.posts?.map(post => ({
...post,
user: selector.users?.find(user => post.userId === user.id),
commetn: selector?.comments?.find(comment => post.id === comment.postId)
}))
return dispatch(
combimePost(
{
post: combinePost,
}
)
)
}, [selector.posts, selector.users, selector.comments])
return <>
{selector?.post?.post?.map((res, i) => (
<div className="card text-center" key={i}>
<div className="card-header">
{res.user?.name}
</div>
<div className="card-body">
<h5 className="card-title">{res.title}</h5>
<p className="card-text">{res.comment?.body}</p>
<button
className="btn btn-outline-danger"
onClick={() => dispatch(removePost(res.id))}
>DELETE</button>
</div>
<div className="card-footer text-muted">
</div>
</div>
)
)}
</>
}
When a post is deleted, all posts disappear from the page, in the console it shows that the selected post has been deleted, but the page is not rendered, it becomes empty. what is the problem ?
When a post is deleted, all posts disappear from the page, in the console it shows that the selected post has been deleted, but the page is not rendered, it becomes empty. what is the problem ?
if you know react (using context api) ,you can look into my issue
this is my github repo : https://github.com/nareshkumar1201/todo-react
i am facing issue , where i wanted to update/toggle the completed property in todo list ---
present state of the app is that -one can add todo ,delete todo , and now i am working on the checkbox -on click of checkbox it should update the completed property in todo list , issue is that when i click on checkbox for the first time it is working fine but if is select/uncheck the todo for the 2nd time its not working ... i don,t know where i am going wrong ??
it will a great help if some one look into issue
import React, { Fragment, useContext } from "react";
import PropTypes from "prop-types";
import TodoContext from "../context/TodoContext";
const TodoItem = ({ todo }) => {
// console.log(todo);
const todoContext = useContext(TodoContext);
const { deleteTodo, updateTodo } = todoContext;
// const { id, todo, completed } = todo;
**const onChange = (e) => {
console.log("todoItem: update req", todo.id);
updateTodo(todo.id);
};**
const onClick = () => {
console.log("todoItem: delete req", todo.id);
deleteTodo(todo.id);
};
return (
<Fragment>
<div className="container col-12 todo-item-container">
<ul className="list-group w-100">
<li className="list-group-item">
**<input
type="checkbox"
name="todo"
// value={todo.todo}
onChange={onChange}
/>**
<span className="text-info"> {todo.todo}</span>
<button className="btn float-right ">
<i
className="fa fa-pencil-square-o text-info "
aria-hidden="true"
></i>
</button>
<button className="btn float-right" onClick={onClick}>
{" "}
<i className="fa fa-trash text-info" aria-hidden="true"></i>
</button>
</li>
</ul>
</div>
</Fragment>
);
};
TodoItem.propTypes = {
todo: PropTypes.object.isRequired,
};
export default TodoItem;
This is state:
import React, { useReducer } from "react";
import TodoContext from "./TodoContext";
import TodoReducer from "./TodoReducer";
import { v1 as uuid } from "uuid";
import {
ADD_TODO,
EDIT_TODO,
DELETE_TODO,
DISPLAY_TODOS,
UPDATE_TODO,
} from "./types";
const TodoState = (props) => {
const initialState = {
todos: [
{
id: 1,
todo: "Do cook food",
completed: false,
},
{
id: 2,
todo: "Clean Room",
completed: false,
},
{
id: 3,
todo: "Wash Car",
completed: false,
},
],
};
const [state, dispatch] = useReducer(TodoReducer, initialState);
//add todo
const addTodo = (todo) => {
todo.id = uuid();
console.log(todo);
// todo.completed = false;
dispatch({ type: ADD_TODO, payload: todo });
};
//display todo
const displayTodos = () => {
dispatch({ type: DISPLAY_TODOS, payload: state.todos });
};
//edit todo
//delete todo
const deleteTodo = (id) => {
console.log("payload id", id);
dispatch({ type: DELETE_TODO, payload: id });
};
//update todo
**const updateTodo = (id) => {
console.log("in todoState", id);
dispatch({ type: UPDATE_TODO, payload: id });
};**
return (
<TodoContext.Provider
value={{
todos: state.todos,
addTodo,
displayTodos,
deleteTodo,
updateTodo,
}}
>
{props.children}
</TodoContext.Provider>
);
};
export default TodoState;
This is reducer:
import {
ADD_TODO,
EDIT_TODO,
DELETE_TODO,
DISPLAY_TODOS,
UPDATE_TODO,
} from "./types";
// import todoContext from "./TodoContext";
const TodoReducer = (state, action) => {
switch (action.type) {
case ADD_TODO:
return {
...state,
todos: [...state.todos, action.payload],
};
case DISPLAY_TODOS:
return {
...state,
todos: action.payload,
};
case DELETE_TODO:
return {
...state,
todos: state.todos.filter((todoObj) => todoObj.id !== action.payload),
};
**case UPDATE_TODO:
console.log("in reducer :", action.payload);
return {
...state,
todos: state.todos.map((todoObj) => {
if (todoObj.id === action.payload) {
todoObj.completed = !todoObj.completed;
}
return todoObj;
}),
};**
default:
return state;
}
};
export default TodoReducer;
Screen images:on click on checkbox for the first time -working fine
if click on 2nd todo checkbox its not working
There was issue with your return in UPDATE_TODO.
I have fixed same for you, Do test and let me know.
While making changes to the object in todos array you need to copy it using the spread operator and then make changes and then return the object.
case UPDATE_TODO:
console.log("in reducer :", action.payload);
return {
...state,
todos: state.todos.map((todoObj) => {
if (todoObj.id === action.payload) {
return { ...todoObj, completed: !todoObj.completed };
}
return todoObj;
}),
};
I have to set a value on from a API into a newly created <button> component handled by Redux, but I don't know if I can use setState for this. I created a reducer and an action SET_VOTE_COUNT but I'm not seeing how this is done. This is my first Redux project, so here is the code:
// ./src/js/components/CounterList.js
import React from 'react';
import { connect } from 'react-redux';
import { increment, decrement } from '../actions/reducer';
import Counter from './Counter';
const CounterList = ({
counters,
onIncrement,
onDecrement
}) => (
<ul>
{counters.map(counter =>
<Counter style={{div: "voting"}}
key={counter.id}
value={counter.count}
onIncrement={() => onIncrement(counter.id)}
onDecrement={() => onDecrement(counter.id)}
/>
)}
</ul>
);
const mapStateToProps = (state) => {
return {
counters: state
};
};
const mapDispatchToProps = (dispatch) => {
return {
onIncrement: (id) => dispatch(increment(id)),
onDecrement: (id) => dispatch(decrement(id))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(CounterList);
// ./src/js/components/Counter.js
import React, { Component } from 'react';
class Counter extends Component {
render() {
return (
<div className="voting">
<span>{this.props.value}</span>
<button
onClick={() => this.props.onIncrement()}>
+
</button>
<button
onClick={() => this.props.onDecrement()}>
-
</button>
</div>
);
}
}
export default Counter;
import React, {Component} from 'react';
import logo from '../../logo.svg';
import '../../App.css';
import AddButton from './AddButton'
class Posts extends Component {
constructor(props) {
super(props);
this.state = {
response: ''
};
}
componentDidMount() {
fetch(
"/posts"
).then(response => response.json())
.then(data => this.setState({ response: data }))
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
{Array.isArray(this.state.response) &&
this.state.response.map(resIndex => <>
{ resIndex.voteScore}
<AddButton className="voting"/>
<p> { resIndex.title }, by { resIndex.author } </p>
<p> { resIndex.body } </p>
<p> {resIndex.category} </p>
</>
)}
</header>
</div>
)
}
}
export default Posts;
import React from 'react';
import { add_counter, setVoteCount } from '../actions/reducer';
import { connect } from 'react-redux';
const AddButton = ({dispatch}) => (
<div className="voting">
<button
onClick={() => {
dispatch(setVoteCount())
// dispatch(add_counter());
}}>
Vote
</button>
</div>
);
export default connect()(AddButton);
The reducer:
// ./src/js/actions/counters.js
export const setVoteCount = (id) => {
return {
type: "SET_VOTE_COUNT",
id
};
}
export const increment = (id) => {
return {
type: "INCREMENT",
id
};
};
export const decrement = (id) => {
return {
type: "DECREMENT",
id
};
};
export const add_counter = () => {
return {
type: "ADD_COUNTER"
};
};
store action:
import { createStore, applyMiddleware, compose } from 'redux';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const change_counter = (state = {}, action) => {
switch (action.type) {
case "SET_VOTE_COUNT":
if (state.id !== action.id) {
return state;
}
return {
...state,
count : 37
}
case "INCREMENT":
if (state.id !== action.id) {
return state;
}
return {
...state,
count: state.count+1
};
case "DECREMENT":
if (state.id !== action.id) {
return state;
}
return {
...state,
count: state.count - 1
};
default:
return state;
}
};
let nextId = 0;
const counters = (state = [], action) => {
switch (action.type) {
case "ADD_COUNTER":
return [...state, {id: nextId++, count: 0}];
case "SET_VOTE_COUNT":
return [...state, {id: nextId++, count: action.count}];
case "INCREMENT":
return state.map(counter => change_counter(counter, action));
case "DECREMENT":
return state.map(counter => change_counter(counter, action));
default:
return state;
}
}
export default createStore(counters, composeEnhancers(applyMiddleware()));
I can upload it to GitHub if necessary. Many thanks.
In the AddButton component,the actions should be wrapped in mapDispatchToProps and passed to the connect function. You are calling the raw action in your example, but you need to wrap it with dispatch for it to update the store.
However, I'm not sure what you are trying to update the store with exactly. The action payload is empty in your example, and the reducer has 37 hardcoded as the state.count in response the SET_VOTE_COUNT action type. Did you mean to pass something from the API response?
<AddButton count={resIndex.count} className="voting"/>
import React from 'react';
import { add_counter, setVoteCount } from '../actions/reducer';
import { connect } from 'react-redux';
const mapDispatchToProps = {
setVoteCount
};
const AddButton = props => (
<div className="voting">
<button onClick={() => {
props.setVoteCount(props.count);
}}>
Vote
</button>
</div>
);
export default connect(null, mapDispatchToProps)(AddButton);