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 .
Related
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
Hi im new to react and i just cant understand why this is not working.
this is my product.js file that im trying to change state when i click on the button "addToBasket"
import React from "react";
import "./Product.css";
import { useStateValue } from "./StateProvider";
function Product({ id, title, image, price, rating }) {
const { dispatch } = useStateValue();
const addToBasket = () => {
dispatch({
type: "ADD_TO_BASKET",
item: {
id: id,
title: title,
image: image,
price: price,
rating: rating,
},
});
};
return (
<div className="product">
<div className="product__info">
<p>{title}</p>
<p className="product__price">
<small>$</small>
<strong>{price}</strong>
</p>
<div className="product__rating">
{Array(rating)
.fill()
.map((_) => (
<p>⭐</p>
))}
</div>
</div>
<img src={image} alt="" />
<button onClick={addToBasket}>Add to basket</button>
</div>
);
}
export default Product;
so i can change one of these states.
i have all the dependencies i need, double checked. Cant understand if its my mistake or some bug
export const initialState = {
basket: [],
user: null,
};
export const getBasketTotal = (basket) =>
basket?.reduce((amount, item) => item.price + amount, 0);
const reducer = (state, action) => {
console.log(action);
switch (action.type) {
case "SET_USER":
return {
...state,
user: action.user,
};
case "ADD_TO_BASKET":
return {
...state,
basket: [...state.basket, action.item],
};
case "REMOVE_FROM_BASKET":
let newBasket = [...state.basket];
const index = state.baslet.findIndex(
(basketItem) => basketItem.id === action.id
);
if (index >= 0) {
newBasket.splice(index, 1);
} else {
}
return { ...state, basket: newBasket };
default:
return state;
}
};
export default reducer;
edit: This is my StateProvider.js
import React, { createContext, useContext, useReducer } from "react";
export const StateContext = createContext();
export const StateProvider = ({ reducer, initialState, children }) => (
<StateContext.Provider value={useReducer(reducer, initialState)}>
{children}
</StateContext.Provider>
);
export const useStateValue = () => useContext(StateContext);
I am learning React/Redux and I am trying to refactor this code from class-based to functional/hooks-based code. The application is an exercise I am working on, it has three components Posts.js where I fetch a list of posts from typicode.com. Each post from the fetched list has a button attacked.
On onClick, it should show details for each post (PostDetails.js and Comments.js):
At the moment, both Posts and Comments are class-based components. I need to:
Step 1: Change them to be functional components and use React Hooks but still keep connect(), mapStateToProps and mapDispatchToProps;
Step 2: Implement React-Redux hooks (UseSelector, useDispatch)
App.js
//imports...
const App = () => {
return (
<div className="container">
<div><Posts /></div>
<div><PostDetails /></div>
</div>
)
}
export default App;
actions
import jsonPlaceholder from '../apis/jsonPlaceholder';
export const fetchPosts = () => async dispatch => {
const response = await jsonPlaceholder.get('/posts');
dispatch({type: 'FETCH_POSTS', payload: response.data})
};
export const selectPost = post => {
return ({
type: 'POST_SELECTED',
payload: post
})
}
export const fetchComments = (id) => async dispatch => {
const response = await jsonPlaceholder.get(`/comments?postId=${id}`);
dispatch({type: 'FETCH_COMMENTS', payload: response.data})
}
reducers
export default (state = [], action) => {
switch (action.type) {
case 'FETCH_POSTS':
return action.payload;
default:
return state;
}
}
export default (selectedPost = null, action) => {
if (action.type === 'POST_SELECTED') {
return action.payload;
}
return selectedPost;
}
export default (state = [], action) => {
switch (action.type) {
case 'FETCH_COMMENTS':
return action.payload;
default:
return state;
}
}
export default combineReducers({
posts: postsReducer,
selectedPost: selectedPostReducer,
comments: commentsReducer
})
components/Posts.js
import React from 'react';
import { connect } from 'react-redux';
import { fetchPosts, selectPost } from '../actions';
import '../styles/posts.scss';
class Posts extends React.Component {
componentDidMount() {
this.props.fetchPosts()
}
renderPosts() {
return this.props.posts.map(post => {
if (post.id <= 10)
return (
<div className='item' key={post.id}>
<div className="title">
<h4>{post.title}</h4>
</div>
<button
onClick={() => {
this.props.selectPost(post)
console.log(post)
}
}>Open</button>
<hr/>
</div>
)
})
}
render() {
return(
<div className="list">
{ this.renderPosts() }
</div>
)
}
}
const mapStateToProps = state => {
return {
posts: state.posts,
selectedPost: state.post
}
};
const mapDispatchToProps = {
fetchPosts,
selectPost
}
export default connect(mapStateToProps, mapDispatchToProps)(Posts);
components/PostDetails.js
import React from 'react';
import { connect } from 'react-redux';
import Comments from './Comments'
const PostDetails = ({ post }) => {
if (!post) {
return <div>Select a post</div>
}
return (
<div className="post-details">
<div className="post-content">
<h3>{post.title}</h3>
<p>{post.body}</p>
<hr/>
</div>
<div className="comments-detail">
<Comments postId={post.id}/>
</div>
</div>
)
}
const mapStateToProps = state => {
return {post: state.selectedPost}
}
export default connect(mapStateToProps)(PostDetails);
components/Comments.js
import React from 'react';
import { connect } from 'react-redux';
import { fetchComments } from '../actions'
class Comments extends React.Component {
componentDidUpdate(prevProps) {
if (this.props.postId && this.props.postId !== prevProps.postId){
this.props.fetchComments(this.props.postId)
}
}
renderComments() {
console.log(this.props.comments)
return this.props.comments.map(comment => {
return (
<div className="comment" key={comment.id}>
<div className="content">
<h5>{comment.name}</h5>
<p>{comment.body}</p>
</div>
<hr />
</div>
)
})
}
render() {
return (
<div className="comments">
{this.renderComments()}
</div>
)
}
}
const mapStateToProps = state => {
return {comments: state.comments}
}
export default connect(mapStateToProps, {fetchComments})(Comments);
This could be a way to create Posts component:
I am assuming that when you dispatch fetchPosts() action, you are saving its response using reducers in Redux.
And, you don't need fetchedPosts in local component state as you already have this data in your Redux state.
const Posts = () => {
const posts = useSelector((state) => state.posts)
const dispatch = useDispatch()
// const [fetchedPosts, setFetchedPosts] = useState([]) // NOT needed
useEffect(() => {
dispatch(fetchPosts())
// setFetchedPosts(posts) // NOT needed
// console.log(posts) // NOT needed, its value may confuse you
}, [])
// Do this, if you want to see `posts` in browser log
useEffect(() => {
console.log(posts)
}, [posts])
/* NOT needed
const renderPosts = () => {
posts.map((post) => {
console.log(post)
})
} */
return (
<>
{posts.map((post) => (
<div key={post.id}>{post.title}</div>
))}
</>
)
}
export default Posts
I'm trying to write a test and I'm not getting the result thats its intended, can someone see what I'm doing wrong here please? I'm trying to call my addTodo but all I get is the error message below, quite new to testing so I'm not sure what it means.
my error message is this:
● <TodoForm /> #addTodo
expect(jest.fn()).toBeCalledWith(...expected)
Expected: {"payload": "a new todo", "type": "addTodo"}
Number of calls: 0
24 | form.find("button").simulate("click");
25 |
> 26 | expect(dispatch).toBeCalledWith({ type: "addTodo", payload: "a new todo" });
| ^
27 | });
28 |
at Object.<anonymous> (src/tests/TodoForm.test.js:26:20)
Here the relevant files:
the test: TodoForm.test.js
import React from "react";
import Enzyme, { mount } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import { Provider } from "../context/TodoContext";
import TodoForm from "../components/TodoForm";
Enzyme.configure({ adapter: new Adapter() });
test("<TodoForm /> #addTodo", async () => {
const dispatch = jest.fn();
const form = mount(
<Provider value={{ dispatch }}>
<TodoForm />
</Provider>
);
form.find("input").simulate("change", { target: { value: "a new todo" } });
form.find("button").simulate("click");
expect(dispatch).toBeCalledWith({ type: "addTodo", payload: "a new todo" });
});
the file I'm trying to write the test for:
import React, { useContext, useState } from "react";
import { Context } from "../context/TodoContext";
import "../css/TodoForm.css";
const TodoForm = ({ initialValues }) => {
const { addTodo } = useContext(Context);
const [title, setTitle] = useState("");
const [error, setError] = useState("");
function handleTodoAdd() {
if (title === "") {
setError(true);
} else {
setError(false);
}
setTitle("");
}
const onChange = e => {
setTitle(e.target.value);
};
/*
function handleSubmitForm(event) {
if (event.keyCode === 13) handleTodoAdd();
}
*/
return (
<div className="container">
<div className="inputContainer">
<div className="input-group">
<input
autoFocus={true}
aria-label="Enter the title of your todo"
placeholder="Enter new todo"
value={title}
onChange={onChange}
/>
<div className="errorContainer">
<span className="error">
{error ? "Please enter a value" : null}
</span>
</div>
<div className="input-group-append">
<button
aria-label="Add todo to your list"
className="addButton"
onClick={() => addTodo(title)}
>
Add
</button>
</div>
</div>
</div>
</div>
);
};
TodoForm.defaultProps = {
initialValues: {
title: "adsda"
}
};
export default TodoForm;
TodoContext.js I'm using this file to pull my provider
import React from "react";
import createDataContext from "./createDataContext";
export const TodoContext = React.createContext();
export default function todoReducer(state, action) {
switch (action.type) {
case "addTodo":
return [
...state,
{ id: Math.floor(Math.random() * 999), title: action.payload }
];
case "deleteTodo":
return state.filter(todo => todo.id !== action.payload);
case "editTodo":
return state.map(todo => {
return todo.id === action.payload.id ? action.payload : todo;
});
default:
return state;
}
}
const addTodo = dispatch => {
return title => {
dispatch({ type: "addTodo", payload: title });
};
};
const deleteTodo = dispatch => {
return id => {
dispatch({ type: "deleteTodo", payload: id });
};
};
const editTodo = dispatch => {
return (id, title) => {
dispatch({ type: "editTodo", payload: { id, title } });
};
};
export const { Context, Provider } = createDataContext(
todoReducer,
{ addTodo, deleteTodo, editTodo },
[]
);
auto creates my context data
import React, { useReducer } from "react";
export default (reducer, actions, initialState) => {
const Context = React.createContext();
const Provider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const boundActions = {};
for (let key in actions) {
boundActions[key] = actions[key](dispatch);
}
return (
<Context.Provider value={{ state, ...boundActions }}>
{children}
</Context.Provider>
);
};
return { Context, Provider };
};
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);