I've set up my Redux to capture a user selection from a webshop (item, size, price) and send it to another Cart component. This is working perfectly, but I want to capture an image of the item and send it to Cart. Within each product page where you can add an item to the cart there is an image that I also would like to send with the user selection. This is an example of the product page component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { addCart } from '../../actions';
import SeltzShirt from './seltzshirt.jpg';
import Slideone from './slideSeltzOne';
import Slidetwo from './slideSeltzTwo';
import RightArrow from './rightarrow';
import LeftArrow from './leftarrow';
export class ProductPage3 extends Component {
constructor(props) {
super(props);
this.state = {
slideCount: 1,
value: 'medium', cartData: {}
}
this.nextSlide = this.nextSlide.bind(this);
this.previousSlide = this.previousSlide.bind(this);
this.handleClick = this.handleClick.bind(this);
this.change = this.change.bind(this);
}
handleClick() {
let cart = {price:25,item:this.description.innerHTML,size:this.state.value};
this.props.onCartAdd(cart);
console.log(cart);
this.itemSelection(cart);
}
...
componentDidMount () {
window.scrollTo(0, 0)
}
render() {
return (
<div className= "ProductPage" id="ProductPage">
<div id='slider'>
{this.state.slideCount === 1 ? <Slideone /> : null}
{this.state.slideCount === 2 ? <Slidetwo /> : null}
<RightArrow nextSlide={this.nextSlide} />
<LeftArrow previousSlide={this.previousSlide} />
</div>
<div id='InfoSquare'>
<div id='wrapper'>
<div id='item' ref={i=>this.description=i}>LOGO TEE</div>
<div id='description'>Black tee 100% cotton with red silkscreened logo on front and back.</div>
<select id="size2" onChange={this.change} value={this.state.value}>
<option value="medium">Medium</option>
<option value="large">Large</option>
<option value="x-large">X-large</option>
</select>
<button onClick={this.handleClick} className="addit">ADD TO CART</button>
</div>
</div>
</div>
);
}
nextSlide() {
this.setState({ slideCount: this.state.slideCount + 1 })
}
previousSlide() {
this.setState({ slideCount: this.state.slideCount - 1 })
}
}
const mapDispatchToProps = (dispatch) => {
return {
onCartAdd: (cart) => {
dispatch(addCart(cart));
},
}
}
function mapStateToProps(state) {
return {
cart: state.cart
};
}
export default connect(mapStateToProps,mapDispatchToProps)(ProductPage3);
This is my Cart component:
import React, { Component } from 'react';
import {addCart} from './Shop';
import { removeCart } from '../../actions';
import { connect } from 'react-redux';
export class Cart extends Component {
constructor(props) {
super(props);
this.state = {items: this.props.cart,cart: [],total: 0};
}
...
render() {
return(
<div className= "Webcart" id="Webcart">
<div id='WebcartWrapper'>
<ul id='webCartList'>
{this.state.items.map((item, index) => {
return <li className='cartItems' key={'cartItems_'+index}>
<h4>{item.item}</h4>
<p>Size: {item.size}</p>
<p>Price: {item.price}</p>
<button onClick={() => this.handleClick(item)}>Remove</button>
</li>
})}
</ul>
<div>Total: ${this.countTotal()}</div>
</div>
</div>
);
}
}
const mapDispatchToProps = (dispatch) => {
return {
onCartAdd: (cart) => {
dispatch(addCart(cart));
},
onCartRemove: (item) => {
dispatch(removeCart(item));
},
}
}
function mapStateToProps(state) {
return { cart: state.cart };
}
export default connect(mapStateToProps, mapDispatchToProps)(Cart);
In Cart I'm rendering the item selection data for each object added to the cart. Here is where I want to display the item image also.
Since I have a image slider set up, an example of one of the slides would be:
import React, { Component } from 'react';
import take1 from './DETAIL.png';
const SlideNocHOne= (props) => {
return <img src= {take1} id="slide"></img>
}
export default SlideNocHOne;
Let's say I want this DETAIL.png image on the Cart, how could I transfer it with the user selection using Redux?
These are my Redux components:
import { createStore, applyMiddleware, compose } from 'redux';
import { persistStore, autoRehydrate } from 'redux-persist';
import reducer from './reducers';
import thunkMiddleware from 'redux-thunk';
import {createLogger} from 'redux-logger';
const store = createStore(
reducer,
undefined,
compose(
applyMiddleware(createLogger(), thunkMiddleware),
autoRehydrate()
)
);
persistStore(store, {whitelist: ['cart']});
export default store;
import {ADD_CART} from './actions';
import {REMOVE_CART} from './actions';
import { REHYDRATE } from 'redux-persist/constants';
export default Reducer;
var initialState = {
cart:{},
data: [],
url: "/api/comments",
pollInterval: 2000
};
function Reducer(state = initialState, action){
switch(action.type){
case REHYDRATE:
if (action.payload && action.payload.cart) {
return { ...state, ...action.payload.cart };
}
return state;
case ADD_CART:
return {
...state,
cart: [...state.cart, action.payload]
}
case REMOVE_CART:
return {
...state,
cart: state.cart.filter((item) => action.payload !== item)
}
default:
return state;
};
}
export const ADD_CART = 'ADD_CART';
export const REMOVE_CART = 'REMOVE_CART';
export function addCart(item){
return {
type: ADD_CART,
payload: item
}
};
export function removeCart(item){
return{
type:REMOVE_CART,
payload: item
}
};
How can I use my Redux setup to transfer the image of a user selection to Cart?
If the path's of your components are relatively stable and you have a single location for the images, you can simply have a function that takes a component's displayName (in your example, Cart, etc.) and returns the relative path the image dir.
If you have that, you can just save a key/value collection in the reducer for what images each component should have, like:
{
CartComponent: ['DETAIL.png', 'DETAIL_2.png']
...
}
When rending just use the mapper function which will provide you a relative path and that's it. Something like (or you can just map out that array):
const relativeImagePath = getRelativeImageDirPathByCompName('CartComponent') + this.props.images.CartComponent[0];
Use require to fetch the image in the template like:
<img src={require(relativeImagePath)} alt="Something"/>
Related
I have a React App with a shopping cart component. I use Redux to update the shopping cart when clicking on a "Add to cart" button in an item. The problem is, even I update the props in the item component, the prop is not updating concurrently. When I'm checking the props in the component in the Chrom developer tools components tab, I can see the props are updating only when I navigate to another component. However, the cart component never receives the updated prop to populate the cart items. These are the necessary components.
Items component
import React, { Component } from 'react';
import ProductData from './DataSet/ProductData';
import { compose } from 'recompose';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { updateCartList } from '../../store/actions';
class LatestProducts extends Component {
addItemToCart = (id) => {
const { cartList, updateCartList } = this.props;
var items = cartList;
ProductData.forEach(each => {
if (each.id === id) {
items.push(each)
}
});
updateCartList(items);
}
render() {
return (
<div>
<div className="itemGridMain">
{
ProductData.map(product => {
return (
<div className="itemCard" key={product.id}>
<button onClick={() => this.addItemToCart(product.id)}>Add to cart</button>
</div>
)
})
}
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
cartList: state.cartList,
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
updateCartList: updateCartList,
}, dispatch);
}
export default compose(connect(mapStateToProps, mapDispatchToProps))(LatestProducts);
Cart component
import React, { Component } from 'react';
import { compose } from 'recompose';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { updateCartList } from '../../store/actions';
class FrontHeader extends Component {
render() {
const { cartList } = this.props;
return (
<div className="cartList">
{
cartList && cartList.length > 0 && cartList.map(item => {
return (
<div className="listItem" key={item.id}>
</div>
)
})
}
</div>
);
}
}
function mapStateToProps(state) {
return {
cartList: state.cartList,
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
updateCartList: updateCartList,
}, dispatch);
}
export default compose(connect(mapStateToProps, mapDispatchToProps))(FrontHeader);
Cart List Reducer
const cartListReducer = (state = [], action) => {
switch (action.type) {
case 'UPDATE_CARTLIST':
return action.payload;
default:
return state;
}
}
export default cartListReducer;
Cart List Index
import cartListReducer from './cartlist';
import { combineReducers } from 'redux';
const allReducers = combineReducers({
cartList: cartListReducer,
})
export default allReducers;
Redux Actions
export const updateCartList = (newCartList) => {
return {
type: 'UPDATE_CARTLIST',
payload: newCartList,
}
}
How can I solve this?
Issue
this.props.cartList is your state and by pushing into that array and saving it back into state you are simply mutating state.
addItemToCart = (id) => {
const { cartList, updateCartList } = this.props;
var items = cartList; // <-- state reference
ProductData.forEach(each => {
if (each.id === id) {
items.push(each) // <-- push into state reference
}
});
updateCartList(items); // <-- saved state reference
}
Solution
You should provide a new array object reference for react to pick up the difference since reconciliation uses shallow object equality.
Your addItemToCart should probably just take the item you want added to the cart and move the cart update logic to the reducer.
LatestProducts
class LatestProducts extends Component {
addItemToCart = (item) => {
const { updateCartList } = this.props;
updateCartList(item); // <-- pass item to action creator
}
render() {
return (
<div>
<div className="itemGridMain">
{ProductData.map(product => {
return (
<div className="itemCard" key={product.id}>
<button
onClick={() => this.addItemToCart(product)} // <-- pass product/item
>
Add to cart
</button>
</div>)
})
}
</div>
</div>
);
}
}
cartListReducer
const cartListReducer = (state = [], action) => {
switch (action.type) {
case 'UPDATE_CARTLIST':
return [...state, action.payload]; // <-- copy state and appen new item
default:
return state;
}
}
I'm fairly new to redux. I'm taking an E-Commerce site tutorial using React and Redux.
I have a CollectionItem Component that has a button that calls an addItem function which adds the specified item to the shopping Cart.
The addItem function works for CollectionItems Component generated from CollectionPreview however it doesnt work for CollectionItems Components generated from Collections. Whenever the button is clicked i get a TypeError: addItem is not a function.
find codes below
CollectionItem Component
import React from "react";
import "./collection.item.style.scss";
import CustomButton from "../custom-button/custom.button.component";
import { connect } from "react-redux";
import { addItem } from "../../redux/cart/cart.action";
export const CollectionItem = ({ item, addItem }) => {
const { name, imageUrl, price } = item;
return (
<div className="CollectionItem">
<div
className="Image"
style={{
backgroundImage: `url(${imageUrl})`
}}
/>
<div className="footer">
<span className="ItemName">{name}</span>
<span className="ItemPrice">#{price}</span>
</div>
<CustomButton onClick={() => addItem(item)} color="outline-dark">
Add to Cart
</CustomButton>
</div>
);
};
const mapDispatchtoProps = dispatch => ({
addItem: item => dispatch(addItem(item))
});
export default connect(null, mapDispatchtoProps)(CollectionItem);
CollectionPreview Component
import React, { Component } from "react";
import CollectionItem from "../collection.item/collection.item.component";
import "./collection.preview.style.scss";
class Preview extends Component {
render() {
const { title, items } = this.props;
return (
<div className="CollectionPreview">
<h1 className="CollectionTitle">{title}</h1>
<div className="Preview">
{items
.filter((item, index) => index < 4)
.map(item => (
<CollectionItem key={item.id} item={item} />
))}
</div>
</div>
);
}
}
export default Preview;
Collection Component
import React, { Component } from "react";
import "./collection.style.scss";
import { connect } from "react-redux";
import { selectCollection } from "../../redux/shop/shop.selector";
import { CollectionItem } from "../collection.item/collection.item.component";
class Collection extends Component {
render() {
const { title, items } = this.props.collections;
return (
<div className="collection-page">
<h2 className="title"> {title}</h2>
<div className="container items">
{items.map(item => (
<CollectionItem key={item.id} item={item} />
))}
</div>
</div>
);
}
}
const mapPropsToState = (state, ownProps) => ({
collections: selectCollection(ownProps.match.params.collectionId)(state)
});
export default connect(mapPropsToState)(Collection);
Redux Cart Action
import { TOGGLE_CART } from "./cart.types";
import { ADD_ITEMS } from "./cart.types";
import { DELETE_ITEMS } from "./cart.types";
import { INCREASE_QUANTITY } from "./cart.types";
import { DECREASE_QUANTITY } from "./cart.types";
export const toggleCart = () => {
return {
type: TOGGLE_CART
};
};
export const addItem = item => {
return {
type: ADD_ITEMS,
payload: item
};
};
export const deleteItem = item => {
return {
type: DELETE_ITEMS,
payload: item
};
};
export const increaseItem = item => {
return {
type: INCREASE_QUANTITY,
payload: item
};
};
export const decreaseItem = item => {
return {
type: DECREASE_QUANTITY,
payload: item
};
};
Cart Reducer
import { TOGGLE_CART } from "./cart.types";
import { ADD_ITEMS } from "./cart.types";
import { addItemToCart } from "./cart.utils";
import { DELETE_ITEMS } from "./cart.types";
import { deleteItemFromCart } from "./cart.utils";
import { increaseCartItem } from "./cart.utils";
import { decreaseCartItem } from "./cart.utils";
import { DECREASE_QUANTITY } from "./cart.types";
import { INCREASE_QUANTITY } from "./cart.types";
const initialState = {
showCart: false,
cartItems: []
};
const cartReducer = (state = initialState, action) => {
switch (action.type) {
case TOGGLE_CART:
return {
...state,
showCart: !state.showCart
};
case ADD_ITEMS:
return {
...state,
cartItems: addItemToCart(state.cartItems, action.payload)
};
case DELETE_ITEMS:
return {
...state,
cartItems: deleteItemFromCart(state.cartItems, action.payload)
};
case INCREASE_QUANTITY:
return {
...state,
cartItems: increaseCartItem(state.cartItems, action.payload)
};
case DECREASE_QUANTITY:
return {
...state,
cartItems: decreaseCartItem(state.cartItems, action.payload)
};
default:
return state;
}
};
export default cartReducer;
Just check your imports, in your Preview component, you use the default import and a named import in your Collection
Thus, in your Collection component, you get a not redux-connected version, so you don’t have access to the props passed in the mapStateToProps.
Just replace import { CollectionItem } with import CollectionItem
I want to push state to the browser and append to the pathname when a subreddit has changed.
In the example below the user chooses an option from ['reactjs', 'frontend']. So when the user chooses reactjs, I want to changethe browser url to: <url>/reddit/reactjs or <url>/reddit/frontend based on the selection.
So when the user goes back and forward, I want to show data that was already fetched.
How can I make it work with react-redux for the example below? Normally, I was using history.pushState(...).
Note: I am using connected-react-router
index.js:
import 'babel-polyfill'
import React from 'react'
import { render } from 'react-dom'
import Root from './containers/Root'
render(<Root />, document.getElementById('root'))
action.js:
import fetch from 'cross-fetch'
export const REQUEST_POSTS = 'REQUEST_POSTS'
export const RECEIVE_POSTS = 'RECEIVE_POSTS'
export const SELECT_SUBREDDIT = 'SELECT_SUBREDDIT'
export const INVALIDATE_SUBREDDIT = 'INVALIDATE_SUBREDDIT'
export function selectSubreddit(subreddit) {
return {
type: SELECT_SUBREDDIT,
subreddit
}
}
export function invalidateSubreddit(subreddit) {
return {
type: INVALIDATE_SUBREDDIT,
subreddit
}
}
function requestPosts(subreddit) {
return {
type: REQUEST_POSTS,
subreddit
}
}
function receivePosts(subreddit, json) {
return {
type: RECEIVE_POSTS,
subreddit,
posts: json.data.children.map(child => child.data),
receivedAt: Date.now()
}
}
function fetchPosts(subreddit) {
return dispatch => {
dispatch(requestPosts(subreddit))
return fetch(`https://www.reddit.com/r/${subreddit}.json`)
.then(response => response.json())
.then(json => dispatch(receivePosts(subreddit, json)))
}
}
function shouldFetchPosts(state, subreddit) {
const posts = state.postsBySubreddit[subreddit]
if (!posts) {
return true
} else if (posts.isFetching) {
return false
} else {
return posts.didInvalidate
}
}
export function fetchPostsIfNeeded(subreddit) {
return (dispatch, getState) => {
if (shouldFetchPosts(getState(), subreddit)) {
return dispatch(fetchPosts(subreddit))
}
}
}
reducers.js:
import { combineReducers } from 'redux'
import {
SELECT_SUBREDDIT,
INVALIDATE_SUBREDDIT,
REQUEST_POSTS,
RECEIVE_POSTS
} from './actions'
function selectedSubreddit(state = 'reactjs', action) {
switch (action.type) {
case SELECT_SUBREDDIT:
return action.subreddit
default:
return state
}
}
function posts(
state = {
isFetching: false,
didInvalidate: false,
items: []
},
action
) {
switch (action.type) {
case INVALIDATE_SUBREDDIT:
return Object.assign({}, state, {
didInvalidate: true
})
case REQUEST_POSTS:
return Object.assign({}, state, {
isFetching: true,
didInvalidate: false
})
case RECEIVE_POSTS:
return Object.assign({}, state, {
isFetching: false,
didInvalidate: false,
items: action.posts,
lastUpdated: action.receivedAt
})
default:
return state
}
}
function postsBySubreddit(state = {}, action) {
switch (action.type) {
case INVALIDATE_SUBREDDIT:
case RECEIVE_POSTS:
case REQUEST_POSTS:
return Object.assign({}, state, {
[action.subreddit]: posts(state[action.subreddit], action)
})
default:
return state
}
}
const rootReducer = combineReducers({
postsBySubreddit,
selectedSubreddit
})
export default rootReducer
configureStore.js
import { createStore, compose, applyMiddleware } from 'redux'
import { createBrowserHistory } from 'history'
import { routerMiddleware } from 'connected-react-router'
import thunkMiddleware from 'redux-thunk'
import logger from 'redux-logger'
import rootReducer from '../reducers'
// const loggerMiddleware = createLogger()
export const history = createBrowserHistory()
export default function configureStore(preloadedState?: any) {
const store = createStore(
rootReducer(history), // root reducer with router state
preloadedState,
compose(
applyMiddleware(
thunkMiddleware,
logger,
routerMiddleware(history), // for dispatching history actions
// ... other middlewares ...
),
),
)
return store
}
Root.js
import React, { Component } from 'react'
import { Provider } from 'react-redux'
import configureStore from '../configureStore'
import AsyncApp from './AsyncApp'
const store = configureStore()
export default class Root extends Component {
render() {
return (
<Provider store={store}>
<AsyncApp />
</Provider>
)
}
}
AsnycApp.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import {
selectSubreddit,
fetchPostsIfNeeded,
invalidateSubreddit
} from '../actions'
import Picker from '../components/Picker'
import Posts from '../components/Posts'
class AsyncApp extends Component {
constructor(props) {
super(props)
this.handleChange = this.handleChange.bind(this)
this.handleRefreshClick = this.handleRefreshClick.bind(this)
}
componentDidMount() {
const { dispatch, selectedSubreddit } = this.props
dispatch(fetchPostsIfNeeded(selectedSubreddit))
}
componentDidUpdate(prevProps) {
if (this.props.selectedSubreddit !== prevProps.selectedSubreddit) {
const { dispatch, selectedSubreddit } = this.props
dispatch(fetchPostsIfNeeded(selectedSubreddit))
}
}
handleChange(nextSubreddit) {
this.props.dispatch(selectSubreddit(nextSubreddit))
this.props.dispatch(fetchPostsIfNeeded(nextSubreddit))
}
handleRefreshClick(e) {
e.preventDefault()
const { dispatch, selectedSubreddit } = this.props
dispatch(invalidateSubreddit(selectedSubreddit))
dispatch(fetchPostsIfNeeded(selectedSubreddit))
}
render() {
const { selectedSubreddit, posts, isFetching, lastUpdated } = this.props
return (
<div>
<Picker
value={selectedSubreddit}
onChange={this.handleChange}
options={['reactjs', 'frontend']}
/>
<p>
{lastUpdated && (
<span>
Last updated at {new Date(lastUpdated).toLocaleTimeString()}.{' '}
</span>
)}
{!isFetching && (
<button onClick={this.handleRefreshClick}>Refresh</button>
)}
</p>
{isFetching && posts.length === 0 && <h2>Loading...</h2>}
{!isFetching && posts.length === 0 && <h2>Empty.</h2>}
{posts.length > 0 && (
<div style={{ opacity: isFetching ? 0.5 : 1 }}>
<Posts posts={posts} />
</div>
)}
</div>
)
}
}
AsyncApp.propTypes = {
selectedSubreddit: PropTypes.string.isRequired,
posts: PropTypes.array.isRequired,
isFetching: PropTypes.bool.isRequired,
lastUpdated: PropTypes.number,
dispatch: PropTypes.func.isRequired
}
function mapStateToProps(state) {
const { selectedSubreddit, postsBySubreddit } = state
const { isFetching, lastUpdated, items: posts } = postsBySubreddit[
selectedSubreddit
] || {
isFetching: true,
items: []
}
return {
selectedSubreddit,
posts,
isFetching,
lastUpdated
}
}
export default connect(mapStateToProps)(AsyncApp)
Picker.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
export default class Picker extends Component {
render() {
const { value, onChange, options } = this.props
return (
<span>
<h1>{value}</h1>
<select onChange={e => onChange(e.target.value)} value={value}>
{options.map(option => (
<option value={option} key={option}>
{option}
</option>
))}
</select>
</span>
)
}
}
Picker.propTypes = {
options: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired,
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired
}
Posts.js:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
export default class Posts extends Component {
render() {
return (
<ul>
{this.props.posts.map((post, i) => (
<li key={i}>{post.title}</li>
))}
</ul>
)
}
}
Posts.propTypes = {
posts: PropTypes.array.isRequired
}
Update:
import { push } from 'connected-react-router';
...
handleChange(nextSubreddit) {
this.props.dispatch(push('/reddit/' + nextSubreddit))
}
I placed this in the handleChange() method. When Picker changes, I push the state to the browser. However, when I go back and forward, the data does not change according to this url. I see the same data in every state.
We can handle this scenario using history property. We implement using listener of history and play with the location property which in turn provide pathname. It would be implement in componentDidUpdate. Everytime when back and forward button of browser clicked, the listener will called and service calls and state can be changed accordingly.
AsyncApp.js
// code here
import { history } from '../configureStore'
// code here
componentDidUpdate(prevProps) {
if (this.props.selectedSubreddit !== prevProps.selectedSubreddit) {
const backBrowser = history.listen(location => {
console.log(location.pathname)
// code here
}
// code here
}
}
I have tried a lot but i couldn't figure out what is the issue.
The props in the component is coming as empty even after adding mapStateToProps and mapDispatchToProps property.Whenever i run the below code i get following error.
projList.js:94 Uncaught TypeError: _this2.props.addNewProj is not a function
My component class is given below:
import React from 'react';
import { addProj } from '../actions';
import { connect } from 'react-redux';
import C from '../constants';
class projList extends React.Component {
constructor(props){
super(props);
this.state = {
title: ''
}
}
render(){
const {title} = this.state;
return(
<section className='proj-list-container'>
<div className='form'>
<label>project Title</label>
<input type='text' onChange={(e)=>{this.setState({title: e.target.value})}}/>
<button className='submit' onClick={()=>{this.props.addNewProj(title)}}>submit</button>
</div>}
</section>
);
}
}
const mapStateToProps = (state, props) =>
({
projLists: state.addProjToList
})
const mapDispatchToProps = dispatch =>
({
addNewProj(projObj) {
dispatch(
addProj(C.ADD_PROJ, projObj)
);
}
});
export default connect (mapStateToProps, mapDispatchToProps)(projList);
export default projList;
My actions file is
import C from './constants'
export const addProj = ({title, endDate}) => {
return ({
type:C.ADD_PROJ,
payload: {
title, endDate
}
})
}
And my store file is :
import C from '../constants';
import { combineReducers } from 'redux';
import {createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
export const addProjToList = (state=[], action) => {
switch (action.type) {
case C.ADD_PROJ :
return [
...state,
action.payload
]
default : return state
}
}
const appReducer = combineReducers({
addProjToList
});
export default (initialState={projList: []}) => {
return applyMiddleware(thunk)(createStore)(appReducer, initialState);
}
any help would be greatly appreciated. Thanks!
I'm doing a simple redux / react todo app. I can't get the todo items to show up. I'm able to console.log the data, but can't get it to appear. What am I doing wrong?
I separated the files, here is my app.js:
import React, { Component } from 'react';
import Todos from './todos';
import TodoList from "./todo_list";
export default class App extends Component {
render() {
return (
<div>
<Todos />
<TodoList/>
</div>
);
}
}
Here is the container Todos:
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { addTodo } from '../actions/index';
class Todos extends Component {
constructor(props) {
super(props);
this.state = {text: ''};
}
addTodo(e) {
e.preventDefault();
this.props.addTodo(this.state.text);
this.setState({
text: ''
});
}
updateValue(e) {
this.setState({text: e.target.value})
}
render() {
return (
<div>
<form onSubmit={(e) => this.addTodo(e)}>
<input
placeholder="Add Todo"
value={this.state.text}
onChange={(e) => {
this.updateValue(e)
}}
/>
<button type="submit">Add Todo</button>
</form>
</div>
);
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({addTodo}, dispatch);
}
export default connect(null, mapDispatchToProps)(Todos);
Here is the TodoList:
import React, {Component} from 'react';
import {connect} from 'react-redux';
class TodoList extends Component {
render() {
return (
<ul>
{ this.props.todo.map((tod) => {
return <li key={tod.message}>{ tod.message }</li>
})}
</ul>
);
}
}
function mapStateToProps({ todo }) {
console.log({ todo });
return { todo };
}
export default connect(mapStateToProps)(TodoList);
Reducer:
import { ADD_TODO } from '../actions/types';
export default function(state=[], action) {
switch(action.type) {
case ADD_TODO:
return [ action.payload.message, ...state ]
}
return state;
}
And action
import { ADD_TODO } from './types';
const uid = () => Math.random().toString(34).slice(2);
export function addTodo(message) {
const action = {
id: uid(),
message: message
};
return {
type: ADD_TODO,
payload: action
};
}
This is what I get from the console.log({todo});
Here is my reducers/index:
import { combineReducers } from 'redux';
import TodosReducer from './reducer_addTodo';
const rootReducer = combineReducers({
todo: TodosReducer
});
export default rootReducer;
It's because there's a disconnect between your TodoList and reducer. TodoList, when mapping, expects each todo to have a message prop, but your reducer, when returning next state, only includes the message in the state array, not an object with the message property:
case ADD_TODO:
return [ action.payload.message, ...state ]
Instead, do not just put the message string in the next state's array, put in the whole object:
case ADD_TODO:
return [ action.payload, ...state ]
Now every single element in the todo array will be an object and have a message and id property. Also, try using an always unique expression for key -- it really shouldn't be the todo message, nor the id you supplied because it's using Math.random which both have a possibility of keys being the same.