I'm having a problem receiving the state on my ActiveMessage object from my reducers. When I click an object on the message list, I can see that it flows through the action, through the correct reducer, but doesn't arrive on the local props for ActiveMessage. I'm not sure why it's not receiving the state on active_message prop. Can anyone point me in the right direction as to why this isnt working?
message_list.js
import React, {Component} from 'react';
import {connect} from 'react-redux';
import MessageStub from "./message_stub";
import {activateMessage} from "../../actions/index";
import {bindActionCreators} from 'redux';
class MessageList extends Component {
constructor(props) {
super(props);
this.state = {
selected_message: null
};
this.selectMessage = this.selectMessage.bind(this);
}
selectMessage(event, index, message){
this.setState({selected_message: index});
this.props.activateMessage(message);
}
renderMessageList() {
return this.props.message_list.map((message, index) =>
<tr key={index}>
<td className={index === this.state.selected_message ? "message-stub-body active-message-stub" : "message-stub-body"}
onClick={(e) => this.selectMessage(e, index, message)}>
<MessageStub />
</td>
<td>
{index === this.state.selected_message && <div className="message-stub-arrow"/>}
</td>
</tr>
);
}
render() {
return (
<div className="message-list">
<table className="message-list-table">
<tbody>
{this.renderMessageList()}
</tbody>
</table>
</div>
);
}
}
function mapStateToProps(state){
return {
message_list: state.message_list,
};
}
function mapDispatchToProps(dispatch){
return bindActionCreators({activateMessage: activateMessage}, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(MessageList);
active_message.js
import React, {Component} from 'react';
import {connect} from 'react-redux';
import MessageOut from "./message_out";
import MessageIn from "./message_in";
class ActiveMessage extends Component {
constructor(props) {
super(props);
this.scrollToBottom = this.scrollToBottom.bind(this);
}
componentDidMount() {
this.scrollToBottom();
}
componentDidUpdate() {
this.scrollToBottom();
}
scrollToBottom() {
this.el.scrollTop = this.el.scrollHeight;
}
renderMessages(){
return this.props.active_message.messages.map((message, i) =>
<MessageOut key={i} onClick={() => {
this.scrollToBottom
}}/>
);
}
render() {
console.log("rendering");
console.log(this.props.active_message);
if(!this.props.active_message){
return <div>loading...</div>
}
return (
<div className="active-message-div">
<div className="active-message"
ref={el => {
this.el = el;
}}>
{this.renderMessages()}
</div>
<div className="message-reply-div">
<textarea className="message-reply-input" placeholder="Type a message..."/>
</div>
</div>
);
}
}
function mapStateToProps(state){
console.log("mapping");
return{
active_message: state.active_message
}
}
export default connect(mapStateToProps)(ActiveMessage);
actions/index.js
export const ACTIVATE_MESSAGE = 'activate_message';
export function activateMessage(message){
console.log("calling action " + message)
return{
type: ACTIVATE_MESSAGE,
payload: message
};
}
reducers/index.js
import { combineReducers } from 'redux';
import MessageListReducer from './reducer_message_list';
import ActiveMessageReducer from './reducer_active_message';
const rootReducer = combineReducers({
message_list: MessageListReducer,
active_message: ActiveMessageReducer,
});
export default rootReducer;
reducer_active_message.js
import {ACTIVATE_MESSAGE} from "../actions/index";
export default function(state = null, action){
switch(action.type){
case ACTIVATE_MESSAGE:
console.log("reducing through ACTIVE_MESSAGE");
return action.payload;
}
return state;
}
reducer_message_list.js
export default function(){
return[
{
user: {
first_name: "First",
last_name: "Last",
type: "P"
},
messages: [
{
message: "hi",
inbound: true
},
{
message: "bye",
inbound: false
}
]
},
{
user: {
first_name: "First2",
last_name: "Last2",
type: "O"
},
messages: [
{
message: "hi",
inbound: true
},
{
message: "bye",
inbound: false
}
]
}
];
}
I have loaded this into CodeSandbox. I'm able to get this to work.
https://codesandbox.io/s/7y9nlvvprq
NOTE: Your reducer_message_list is not a reducer, just a static data set.
Related
So when I run this and check the checkbox, I can see the values changing in the state, but why is the checkbox control not changing its status from check/uncheck? I know the render() method is being hit as well. Why, oh why, Gods of code? Lost in hours of figuring out what's wrong and I'm lost!
bob-Todos.js FILE
class Todo extends React.Component {
constructor(param) {
super();
this.state = {
id: param.data.id,
text: param.data.text,
completed: param.data.completed,
onMyChange: param.OnChange,
};
}
render() {
console.log("In TODO Render");
return (
<div>
<p>
<input
type="checkbox"
onChange={() => {
this.state.onMyChange(this.state.id);
}}
checked={this.state.completed}
/>
{this.state.text}
</p>
</div>
);
}
}
export default Todo;
Bob-App.js FILE
import React, { Component } from "react";
import Todo from "./bob-Todo";
import todoData from "../data/bob-todosData";
class App extends Component {
constructor() {
super();
this.state = { data: todoData };
this.OnChange = this.OnChange.bind(this);
}
OnChange(myId) {
this.setState((prev) => {
let updatedTodos = prev.data.map((todo) => {
if (todo.id === myId) {
todo.completed = !todo.completed;
}
return todo;
});
return { data: updatedTodos };
});
console.log(this.state.data);
}
render() {
return this.state.data.map((item) => {
return <Todo key={item.id} data={item} OnChange={this.OnChange} />;
});
}
}
export default App;
bob-todosData.js FILE
const todosData = [
{
id: 1,
text: "take out the trash",
completed: true
},
{
id: 2,
text: "rest for a while and relax",
completed: false
},
{
id: 3,
text: "watch an online movie",
completed: true
}
]
export default todosData
index.js FILE
import React from 'react';
import ReactDOM from 'react-dom';
import AppBob from "./bobComponents/Bob-App";
ReactDOM.render(
<AppBob />, document.getElementById('root')
);
You don't need to assign your props to state in your Todo component
Just remove them and invoke the function also use those variables directly:
Then your component will be:
class Todo extends React.Component {
render() {
const {
data: {
id,
text,
completed,
},
OnChange, // <-- Should rename this to "onChange"
} = this.props;
console.log('In TODO Render');
return (
<div>
<p>
<input
type="checkbox"
onChange={() => {
OnChange(id);
}}
checked={completed}
/>
{text}
</p>
</div>
);
}
}
export default Todo;
Also, rename your OnChange function to onChange to enable js convention
I'm managing Todo lists in my app. The main view is a page with all the lists displayed as cards. If you click on one of them, you can modify, update, delete stuff through a modal that appears.
I have a TodoLists reducer that store all the TodoLists. I don't know how to handle the modal. Should I use redux or just local state?
import _ from "lodash";
import React from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { listsActions } from "../duck";
import NewList from "./NewList";
import Card from "./Card";
import Modal from "./Modal";
class Lists extends React.Component {
constructor(props) {
super(props);
this.state = {
modal: false,
list: {}
};
this.hideModal = this.hideModal.bind(this);
this.renderModal = this.renderModal.bind(this);
}
componentDidMount() {
const { fetchByUserId, user } = this.props;
if (user !== undefined) {
fetchByUserId(user.id);
}
}
hideModal() {
this.setState({
modal: false
});
}
renderModal() {
this.setState({
modal: true
});
}
render() {
const { items } = this.props;
const { modal, list } = this.state;
return (
<div>
<NewProject />
<div className="columns">
{_.map(items, (l) => (
<div
key={l.id}
className="column"
>
<Card
list={l}
onClick={() => this.renderModal(l)}
/>
</div>
))}
</div>
<Modal
className={modal ? "is-active" : ""}
list={list}
onClose={this.hideModal}
/>
</div>
);
}
}
const mapStateToProps = (state) => {
const { user } = state.authentication;
const { items, loading, error } = state.lists;
return {
user,
items,
loading,
error
};
};
export default connect(
mapStateToProps,
{ fetchByUserId: listsActions.fetchByUserId }
)(Projects);
I'm new to the reagent and Redux. I am trying to make the menu, but the console getting error:
App.js?eb5a:12 Uncaught TypeError: Cannot read property 'handleClickMenu' of undefined
How to fix the error?
As the payload in a new state record?
App.js:
import React, { Component } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import User from '../components/User'
import Page from '../components/Page'
import BottomMenu from '../components/BottomMenu'
import * as pageActions from '../actions/PageActions'
import * as userActions from '../actions/UserActions'
import * as bmenuActions from '../actions/BottomMenuActions'
class App extends Component {
render() {
const { user, page, bottomMenu } = this.props
const { getPhotos } = this.props.pageActions
const { handleClickMenu } = this.props.bmenuActions
const { handleLogin } = this.props.userActions
return <div className='row'>
<Page photos={page.photos} year={page.year} getPhotos={getPhotos} fetching={page.fetching} error={page.error}/>
<User name={user.name} handleLogin={handleLogin} error={user.error} />
<BottomMenu selectedItem={bottomMenu.selectedItem} bmenuClick={() => handleClickMenu} />
</div>
}
}
function mapStateToProps(state) {
return {
user: state.user,
page: state.page,
bottomMenu: state.bottomMenu
}
}
function mapDispatchToProps(dispatch) {
return {
pageActions: bindActionCreators(pageActions, dispatch),
userActions: bindActionCreators(userActions, dispatch),
bmenuActions: bindActionCreators(bmenuActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
component bottomMenu.js:
import React, { PropTypes, Component } from 'react'
import BottomMenuItem from '../components/BottomMenuItem'
export default class BottomMenu extends Component {
render() {
const { selectedItem, bmenuClick } = this.props;
const menuItems = [{
url: 'home',
name: 'Главная страница'
}, {
url: 'goods',
name: 'Объем поставок'
}, {
url: 'geo',
name: 'География поставок'
}, {
url: 'clients',
name: 'Клиенты и партнеры'
}, {
url: 'production',
name: 'Виды продукции'
}, {
url: 'cost',
name: 'Рассчитайте стоимость'
}, {
url: '',
name: 'Свяжитесь с нами'
}];
return <ul className='footer-menu'>
{ menuItems.map((item,index) => <BottomMenuItem key={index} item={item} selected={index === selectedItem} bmenuClick={bmenuClick} /> )}
</ul>
}
}
BottomMenu.propTypes = {
item: PropTypes.object,
selected: PropTypes.bool
}
component BottomMenuItem.js:
import React, { PropTypes, Component } from 'react'
export default class BottomMenuItem extends Component {
bmenu_Click(e){
e.preventDefault()
this.props.bmenuClick(e)
}
render() {
const { item, selected} = this.props
const className = 'footer-menu__li footer-menu__li--' + item.url + (selected ? ' footer-menu__li--current' : '')
return <li className={className}>
{!selected ?
<a href={item.url} className='footer-menu__href' onClick={::this.bmenu_Click}>{item.name}</a>
:
<span className='footer-menu__href'>{item.name}</span>
}
</li>
}
}
BottomMenuItem.propTypes = {
item: PropTypes.object.isRequired,
selected: PropTypes.bool.isRequired,
bmenuClick: PropTypes.func.isRequired
}
action: BottomMenuAction.js
export function handleClickMenu(el) {
console.log(el)
return function(dispatch) {
dispatch({
type: 'bmenuClick',
payload:''
})
}
}
A few mistakes in your code
First is a typo where you bindAction as bmenuActions and then in your App.js you are using it with the wrong case as const { handleClickMenu } = this.props.bMenuActions. You need to change that to const { handleClickMenu } = this.props.bmenuActions
Also now since here handleClickMenu is a function you need to bind it while passing down to BottomMenu component like
<BottomMenu selectedItem={bmenu.selectedItem} bmenuClick={() => handleClickMenu} />
Now again in App.js your state is available as bottomMenu and you are resolving it like bmenu. Change it to
const { user, page, bottomMenu } = this.props
Now from BottomMenu component you are sending the props as bmenuClick to the BottomMenuItem component like
return <ul className='footer-menu'>
{ menuItems.map((item,index) => <BottomMenuItem key={index} item={item} selected={item === selectedItem} bmenuClick={() => bmenuClick }/> )}
</ul>
but you are using it in your BottomMenuItem component as this.props.handleClickMenu here
bmenu_Click(e){
this.props.handleClickMenu(e)
}
You need to change it as
bmenu_Click(e){
this.props.bmenuClick(e)
}
Thank you, I've done:
<BottomMenuItem key={index} item={item} pos={index} selected={index === selectedItem} bmenuClick={bmenuClick} />
and
<a href={item.url} className='footer-menu__href' onClick={::this.bmenu_Click} data-pos={pos}>{item.name}</a>
I have a simple Cart component and I want to show either a "Your cart is empty" message when there are no items in it.
import React, { Component } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import * as CartActions from '../actions/cart'
import Shelf from './Shelf'
import EmptyCart from './EmptyCart'
/*
This is a container component
*/
class Cart extends Component {
constructor(props) {
super(props)
this.state = {
itemQuantity: props.cart.length
}
}
render() {
const CartItems = this.props.cart.map(
(item, idx) =><li key={idx}>{item.name} - ${item.price}</li>
)
const isCartEmpty = () => this.state.itemQuantity === 0
console.log("is cart empty? ", isCartEmpty(), "cart item quantity ", this.state.itemQuantity)
return(
<div className="Cart">
<Shelf addItem={this.props.action.addToCart} />
<h2>Cart Items</h2>
<ol>
{ isCartEmpty() ? <EmptyCart/> : {CartItems} }
</ol>
</div>
)
}
}
function mapStateToProps(state, prop) {
return {
cart: state.cart
}
}
function mapDispatchToProps(dispatch) {
return {
action: bindActionCreators(CartActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Cart)
My Shelf component looks like this:
import React, { Component } from 'react';
class Shelf extends Component {
constructor(props) {
super(props)
this.addItemToCart = this.addItemToCart.bind(this)
this.state = {
shelfItems: [
{ "name": 'shampoo', "price": 23 },
{ "name": 'chocolate', "price": 15 },
{ "name": 'yogurt', "price": 10 }
]
}
}
addItemToCart(item){
this.props.addItem(item)
}
render() {
const shelfItems = this.state.shelfItems.map((item, idx) => {
return <li key={idx}><button onClick={()=>this.addItemToCart(item)}>[+]</button>{item.name} - ${item.price}</li>
})
return(
<div>
<h2>Shelf</h2>
<ul>
{shelfItems}
</ul>
</div>
)
}
}
export default Shelf
Cart Reducer:
export default(state = [], payload) => {
switch (payload.type) {
case 'add':
return [...state, payload.item]
default:
return state
}
}
addToCart action:
export const addToCart = (item) => {
return {
type: 'add',
item
}
}
The empty message shows up but the list does not update when I add items. What am I doing wrong? The code works just fine if I remove the conditionals and just render CartItems
It's because you set only initial state. When you add item you don't set a new state. If you use redux there is no local state needed.
Try this:
class Cart extends Component {
constructor(props) {
super(props)
this.state = {}
}
render() {
const CartItems = this.props.cart.map(
(item, idx) =><li key={idx}>{item.name} - ${item.price}</li>
)
const isCartEmpty = CartItems.length === 0
return(
<div className="Cart">
<Shelf addItem={this.props.action.addToCart} />
<h2>Cart Items</h2>
<ol>
{isCartEmpty ? <li>Your Cart is Empty</li> : CartItems}
</ol>
</div>
)
}
}
I have to render few tabs. ONclick of it, it should get highlighted. I have tabList object coming from reducer (hard-coded value). onclicking of a tab a action is generated which set the "state" with clicked tab object(I call it "activeTab").
while rendering the tabList (all the tabs) I am checking if "rendered_tabid == active_tabid" then add a class "active" (it is conditional basis)
active-tab-reducer.js
export const tabReducer = () => {
return [
{
id: 1,
name:"Dental Page",
url: '#dentalPage'
},
{
id: 2,
name:"Vision Page",
url: '#visionPage'
},
{
id: 3,
name:"Other page Tab",
url: '#OtherPage'
}
]
}
const activeTabReducer = (state = {}, action) => {
switch(action.type) {
case "TAB_SELECTED": return action.payload;
break;
}
return state;
}
export default activeTabReducer;
(combined-reducer) index.js
import {combineReducers} from 'redux';
import activeTabReducer, {tabReducer} from './active-tab-reducer';
const allReducers = combineReducers({
tabList: tabReducer,
activeTab: activeTabReducer
});
export default allReducers;
(action) index.js
export const selectTab = (tab) => {
console.log('action invoked', tab);
return {
type: "TAB_SELECTED",
payload: tab
}
}
tablist.js
import React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {selectTab} from '../actions/index';
import TabsListItem from './tabsListItem';
class TabList extends React.Component {
constructor(props){
super(props);
}
createTabItems(){
return this.props.tabList.map((item, i) => {
return (
<TabsListItem key={i} tabList={item} />
)
});
}
render() {
return (
<div id="layout-header" className="layout-header">
<div id="header" className="header">
<ul className="tabs tabs--horizontal">
{this.createTabItems()}
</ul>
</div>
</div>
);
}
};
function mapStateToProps(state) {
return {
tabList: state.tabList,
activeTab: state.activeTab
}
}
function matchDispatchToProps(dispatch){
return bindActionCreators({selectTab: selectTab}, dispatch);
}
export default connect(mapStateToProps, matchDispatchToProps)(TabList);
tabListItem.js
import React from 'react';
class TabListItem extends React.Component {
constructor(props){
super(props);
console.log(this.props.tabList)
}
render() {
return (
<li onClick={() => this.props.selectTab(this.props.tabList)}
className={"tab "+((this.props.activeTab.id==item.id)?'active':'')+""+( (this.props.activeTab.id==undefined) && (item.id == 1)?'active':'' )
role="presentation" className={"tab " }>
<a href="#">
<div className="tab__label">
<div className="tab__label__value">{this.props.tabList.name}</div>
</div>
</a>
</li>
);
}
};
export default TabListItem;
when I click any tab (from tabListItem), a action TAB_SELECTED action should dispatch, which set the state with "activeTab" object.
How to generate action from child?
You should pass a function to the child component via props.
As the action has a parameter to select the correct tab, you can use a function returning a function:
createTabItems() {
return this.props.tabList.map((item, i) => {
return (
<TabsListItem key={i} tabList={item} onSelect={() => this.onSelect(i).bind(this)} />
);
});
}
In this way your child component calls your method onSelect passing the correct parameter.
In your onSelect method on the parent (container) component you will then dispatch your action:
onSelect(i) {
this.props.selectTab(i);
}