I'm pretty new to React and Redux and very new to react-dnd, and I think I'm doing something wildly incorrect here. Although there are other similar posts out there I can't quite find a solution in them.
I'm working on a Kanban board app that is somewhat based on the one found at https://survivejs.com/react/implementing-kanban/drag-and-drop/ though that version uses Alt.js and I'm using Redux.
The problem: when dragging a component, the action function is called but the case in the reducer (MOVE_TICKET) is not. This seems to be the case regardless of the content of the action function.
I linked the action to a click event and in this instance the action and reducer worked as expected. This leads me to think that it must be a problem with the way I've set up the Ticket component with the dnd functions.
Ticket.js:
import React from "react"
import {compose} from 'redux';
import { DragSource, DropTarget } from 'react-dnd';
import ItemTypes from '../constants/ItemTypes';
import { moveTicket } from "../actions/ticketsActions"
const Ticket = ({
connectDragSource, connectDropTarget, isDragging, isOver, onMove, id, children, ...props
}) => {
return compose (connectDragSource, connectDropTarget)(
<div style={{
opacity: isDragging || isOver ? 0 : 1
}} { ...props } className = 'ticket'>
<h3 className = 'summary'> { props.summary } </h3>
<span className = 'projectName'> { props.projectName }</span>
<span className = 'assignee'> { props.assignee } </span>
<span className = 'priority'> { props.priority } </span>
</div>
);
};
const ticketSource = {
beginDrag(props) {
return {
id: props.id,
status: props.status
};
}
};
const ticketTarget = {
hover(targetProps, monitor) {
const targetId = targetProps.id;
const sourceProps = monitor.getItem();
const sourceId = sourceProps.id;
const sourceCol = sourceProps.status;
const targetCol = targetProps.status;
if(sourceId !== targetId) {
targetProps.onMove({sourceId, targetId, sourceCol, targetCol});
}
}
};
export default compose(
DragSource(ItemTypes.TICKET, ticketSource, (connect, monitor) => ({
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging()
})),
DropTarget(ItemTypes.TICKET, ticketTarget, (connect, monitor) => ({
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver()
}))
)(Ticket)
ticketsReducer.js:
export default function reducer(state={
tickets: [],
fetching: false,
fetched: false,
error: null,
}, action) {
switch (action.type) {
case "MOVE_TICKET": {
return [{...state, tickets: action.payload}]
}
}
return state
}
ticketsActions.js
import store from '../store';
export function moveTicket({sourceId, targetId, sourceCol, targetCol}) {
const columns = Object.assign({}, store.getState().tickets.tickets)
const sourceList = columns[sourceCol];
const targetList = columns[targetCol];
const sourceTicketIndex = sourceList.findIndex(ticket => ticket.id == sourceId);
const targetTicketIndex = targetList.findIndex(ticket => ticket.id == targetId);
if(sourceCol === targetCol){
var arrayClone = sourceList.slice();
arrayClone.splice(sourceTicketIndex, 1);
arrayClone.splice(targetTicketIndex, 0, sourceList[sourceTicketIndex]);
columns[sourceCol] = arrayClone;
}
return function(dispatch){
dispatch({type: "MOVE_TICKET", payload: columns});
}
}
Column.js (where each Ticket component is rendered)
import React from "react"
import uuid from "uuid"
import { connect } from "react-redux"
import ColumnsContainer from "./ColumnsContainer"
import Ticket from "./ticket"
import { moveTicket } from "../actions/ticketsActions"
#connect((store) => {
return {
columns: store.columns.columns
};
})
export default class Column extends React.Component {
console(){
console.log(this)
}
render(){
const tickets = this.props.tickets.map((ticket, id) =>
<Ticket
key = {uuid.v4()}
id={ticket.id}
summary = { ticket.summary }
assignee = { ticket.assignee }
priority = { ticket.priority }
projectName = { ticket.displayName }
onMove={ moveTicket }
status= { ticket.status }
/>
)
return(
<div key = {uuid.v4()} className = { this.props.className }>
<h2 key = {uuid.v4()}>{ this.props.title }</h2>
<ul key = {uuid.v4()}>{ tickets }</ul>
</div>
)
}
}
If anyone can see where I'm going wrong I could really use some assistance.
You are not connecting the moveTicket action to redux's dispatcher.
You'll have to do something like:
#connect((store) => {
return {
columns: store.columns.columns
};
}, {moveTicket})
export default class Column extends React.Component {
// ...
// use this.props.moveTicket instead of moveTicket
The second parameter to connect is called mapDispatchToProps, which will do the dispatch(actionFn) for you.
You might want to name the bound action differently, e.g.
#connect((store) => {
return {
columns: store.columns.columns
};
}, {connectedMoveTicket: moveTicket})
// then use this.props.connectedMoveTicket
Related
So I have TodoList component which is updated just fine when I add new Item. Inside I have TodoItem width delete and togglecomplete functions and they do invoke methodsof my todoList object, I see in console that object is changing, yet List component won't get rerendered. Any idia what can be done.
package json: "mobx": "^6.7.0", "mobx-react-lite": "^3.4.0",
import { ITodo } from '../type/ITodo';
import TodoItem from '../TodoItem/TodoItem.component'
import './TodoList.styles.css';
import { observer } from 'mobx-react-lite';
type Props = {
todoList: ITodo[];
}
const TodoList: React.FC<Props> = ({ todoList }) => {
return (
<div className="TodoList">
{
todoList && todoList.length > 0 && (
todoList.map(el => (
<TodoItem content={el} key={el.id} />
))
)
}
</div>
)
}
export default observer(TodoList);
here are delete and toggle complete functions
import { useStores } from '../Store/StoreContext';
import { observer } from 'mobx-react-lite';
type Props = {
content: ITodo;
}
const TodoItem: React.FC<Props> = ({ content }) => {
const { todoList } = useStores();
const { deadline, title , description, completed, id } = content
const handleChange = () => {
todoList.toggleComplete(id)
}
const deleteHandler = () => {
todoList.deleteTodo(id);
}
return (
<div className="TodoItem">
<div className="actions">
<FormControlLabel
onChange={handleChange}
/>
<Button
variant="outlined"
onClick={deleteHandler}
>
Delete
</Button>
</div>
</div>
)
}
export default observer(TodoItem);
and here is my store just in case, I keep my store in Context.
import { action, makeObservable, observable } from "mobx";
import { ITodo } from "../type/ITodo";
export class Todo {
todos: ITodo[] = [];
constructor() {
makeObservable(this, {
todos: observable,
getTodos: action,
addTodo: action,
deleteTodo: action,
toggleComplete: action,
});
}
getTodos() {
return this.todos;
}
addTodo(todo: ITodo) {
this.todos.push(todo);
}
deleteTodo(id: string) {
console.log(id);
this.todos = this.todos.filter((el) => el.id !== id);
}
toggleComplete(id: string) {
this.todos = this.todos.map((el) => {
if (el.id !== id) {
return el;
}
return { ...el, completed: !el.completed };
});
}
}
Here is repository on github: https://github.com/pavel-gutsal/mobx-todo-list
node version - 16.xx
I've been trying to learn the MERN stack by following code using Traversy Media's MERN stack tutorial. I'm currently on Part 6 where I am trying to delete an item using Redux; however, when I click the item to delete it, nothing happens.
I've been trying to figure out what is wrong using the Redux DevTools on Chrome and when I try to delete an item, under the "Action" tab DELETE_ITEM appears and the action has the type : "DELETE_ITEM" and payload with the id of the item I clicked, which all seems correct.
However, when I go to check the "Diff" tab, it just says (states are equal) which I'm assuming means that everything is working correctly until I reach the itemReducers.js because the state isn't showing up.
I've checked and rechecked the code for any typos but it all seems correct to me. Maybe I'm just missing something as I've been messing around with the code for the longest time trying to figure out what is wrong?
ShoppingList.js
import React, { Component } from 'react';
import { Container, ListGroup, ListGroupItem, Button } from 'reactstrap';
import { PropTypes } from "prop-types";
import uuid from 'uuid';
import { connect } from "react-redux";
import { getItems, deleteItem } from "../actions/itemActions";
const mapStateToProps = (state) => ({
item : state.item
});
class ShoppingList extends Component {
componentDidMount() {
this.props.getItems();
}
onDeleteClick = id => {
this.props.deleteItem(id);
};
render() {
const { items } = this.props.item;
return (
<Container>
<Button
style={{ margin: '2rem 0' }}
onClick={() => {
const name = prompt('Enter new item');
if (name) {
this.setState(state => ({
items : [...state.items, { id : uuid.v4(), name }]
}))
}
}}>
Add Item
</Button>
<ListGroup>
{items.map(({id, name}) => {
return (
<ListGroupItem key={ id }>
{ name }
<Button
style={{ float: 'right' }}
onClick={this.onDeleteClick.bind(this, id)}>×</Button>
</ListGroupItem>
)
})}
</ListGroup>
</Container>
);
}
}
ShoppingList.propTypes = {
getItems : PropTypes.func.isRequired,
item : PropTypes.object.isRequired
}
export default connect(mapStateToProps, { getItems, deleteItem })(ShoppingList);
itemActions.js
export const getItems = () => {
return {
type: "GET_ITEMS"
}
}
export const addItem = () => {
return {
type : "ADD_ITEM"
}
}
export const deleteItem = id => {
return {
type : "DELETE_ITEM",
playload: id
}
}
itemReducer.js
import uuid from 'uuid';
const initialState = {
items : [
{ id: uuid.v4(), name : 'steak'},
{ id: uuid.v4(), name : 'chicken'},
{ id: uuid.v4(), name : 'eggs'}
]
}
const itemReducer = (state = initialState, action) => {
switch (action.type) {
case "GET_ITEMS" :
return {...state};
case "ADD_ITEM" :
state = {};
break;
case "DELETE_ITEM" :
return {
...state,
items : state.items.filter(item => item.id !== action.payload)
};
default :
return {...state};
}
}
export default itemReducer;
store.js
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
import thunk from 'redux-thunk';
import itemReducer from './reducers/itemReducer';
const initialState = {};
const middleware = [thunk];
let store = createStore(combineReducers({item : itemReducer}), initialState, compose(
applyMiddleware(...middleware),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
));
export default store;
I am managing the states of my component using redux and accessing the array data for the component from redux store using a new map. I need to be able to get my icon's value to increment when clicked. I am not sure how to get access to the values of my reactions to make this work and handle my click event. I need to update the reactions of my card using the updateUploadReaction function. I want the value of the reactions to increment by 1 when the icon is clicked
I wrote out two lines of pseudo-code for a better understanding of the possible ways to go about it. Here's a link if you need to access the code directly. It is under Home directory
reducers.js
import { UPDATE_REACTION, REQUEST_UPLOAD_LIST } from './actionTypes';
import { INITIAL_STATE, USER_UPLOADS } from './constants';
/**
* Creates a Javascript Map with the user uploads mapped by id
*
* #param {Array} USER_UPLOADS - a users uploads
* #return {Map} - the user uploads
*/
function generateUploadsMap() {
const uploads = new Map();
USER_UPLOADS.forEach(userUpload => {
const { id } = userUpload;
uploads.set(id, userUpload);
});
return uploads;
}
function updateUploadReaction(id, reaction, uploads) {
const updatedUploads = new Map([...uploads.entries()]);
const userUpload = updatedUploads.get(id);
const { type } = updatedUploads.reaction;
// I need to get access values and increment
// Set reaction by type to value
*// value.set(type, reaction);*
updatedUploads.set(id, userUpload);
return updatedUploads;
}
export default (state = { ...INITIAL_STATE }, action) => {
switch (action.type) {
case REQUEST_UPLOAD_LIST: {
return {
...state,
uploads: generateUploadsMap(),
};
}
case UPDATE_REACTION: {
const { uploads } = state;
return {
...state,
uploads: updateUploadReaction(action.id, action.reaction, uploads),
};
}
default:
return state;
}
};
constants.js
/** #constant */
export const INITIAL_STATE = {
uploads: new Map(),
};
export const USER_UPLOADS = [
{
id: 0,
// eslint-disable-next-line max-len
image: 'http://sugarweddings.com/files/styles/width-640/public/1.%20The%20Full%20Ankara%20Ball%20Wedding%20Gown%20#therealrhonkefella.PNG',
reactions: {
dislike: 0,
like: 0,
maybe: 0,
},
story: "It's my birthday next week! What do you think?",
user: 'Chioma',
},
{
id: 1,
// eslint-disable-next-line max-len
image: 'https://dailymedia.com.ng/wp-content/uploads/2018/10/7915550_img20181007141132_jpeg01c125e1588ffeee95a6f121c35cd378-1.jpg',
reactions: {
dislike: 0,
like: 0,
maybe: 0,
},
story: 'Going for an event. Do you like my outfit?',
user: 'Simpcy',
},
{
id: 2,
// eslint-disable-next-line max-len
image: 'https://i0.wp.com/www.od9jastyles.com/wp-content/uploads/2018/01/ankara-styles-ankara-styles-gown-ankara-tops-ankara-gowns-ankara-styles-pictures-latest-ankara-style-2018-latest-ankara-styles-ankara-ankara-styles.png?fit=437%2C544&ssl=1',
reactions: {
dislike: 0,
like: 0,
maybe: 0,
},
story: 'Saturdays are for weddings. Yay or nay?',
user: 'Angela',
},
];
Home.js
import PropTypes from 'prop-types';
import React from 'react';
import { Avatar, Card, Icon, List } from 'antd';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { LIST_TEXTS, STYLES } from '../constants';
import * as actions from '../actions';
import { getUploads } from '../selectors';
const { AVATAR, CARD_CONTAINER, CARD_LIST, ICON, USER_LIST } = STYLES;
const { INNER, MORE, UPLOAD, VERTICAL } = LIST_TEXTS;
const IconText = ({ type, text }) => (
<span>
<Icon type={type} style={ICON} />
{text}
</span>
);
function createReactionsIcon(item, updateReaction) {
const { like, dislike, maybe } = item.reactions;
const icons = [
{ reaction: 'like', text: `${like}`, type: 'heart' },
{ reaction: 'dislike', text: `${dislike}`, type: 'dislike' },
{ reaction: 'maybe', text: `${maybe}`, type: 'meh' },
];
return icons.map(({ reaction, text, type }) => (
<IconText
onClick={() => updateReaction(item.id, reaction)}
key={reaction}
type={type}
text={text}
/>
));
}
class Home extends React.Component {
componentDidMount() {
const { actions: { requestUploadList } } = this.props;
requestUploadList();
}
updateReaction = (id, reaction) => {
const { actions: { updateReaction } } = this.props;
updateReaction(id, reaction);
}
render() {
const { uploads } = this.props;
const values = [...uploads.values()];
return (
<div style={CARD_CONTAINER}>
<List
itemLayout={VERTICAL}
dataSource={values}
renderItem={item => (
<List.Item style={USER_LIST}>
<Card
actions={createReactionsIcon(item, this.updateReaction)}
cover={<img alt={UPLOAD} src={item.image} />}
extra={<Icon type={MORE} />}
hoverable
title={(
<a href="/">
<Avatar src={item.image} style={AVATAR} />
{item.user}
</a>
)}
type={INNER}
style={CARD_LIST}
>
{item.story}
</Card>
</List.Item>
)}
/>
</div>
);
}
}
Home.propTypes = {
actions: PropTypes.object,
uploads: PropTypes.instanceOf(Map),
};
const mapStateToProps = state => ({
uploads: getUploads(state),
});
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(actions, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Two things to make things working in code -
1) Home.jsx - You are not using onClick in IconText component only passing it.
const IconText = ({ type, text, onClick }) => {
return (
<span>
<Icon type={type} style={ICON} onClick={onClick} />
{text}
</span>
);
};
2) Reducers.js - I am not sure where you want to write incremental logic, I have just written it inside reducer and is working.
function updateUploadReaction(id, reaction, uploads) {
const updatedUploads = new Map([...uploads.entries()]);
const userUpload = updatedUploads.get(id);
userUpload.reactions[reaction] = userUpload.reactions[reaction] + 1;
updatedUploads.set(id, userUpload);
return updatedUploads;
}
At last working codesanbox link
Hope that helps!!!
I'm trying to add an element to an array in an object in my Redux store. I see the object get added to the store but it is not updating the component. If I leave the page and return it is showing up.
I'm pretty sure this is a state mutation issue but I can't figure out where I'm going wrong unless I fundamentally misunderstand what Immer is doing. In the component I'm using produce to add the string to the array, passing the new object to my reducer and using produce to add that object to an array of those objects.
I've looked through a ton of similar questions that all relate to state mutation, but the way I understand it the return from the component's call to produce should be a fully new object. Then in the reducer the call to produce should be returning a new object array.
This is the first time using Immer in a large project so it's entirely possible I don't fully get how it's working it's magic.
Component
import produce from 'immer';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import uuid from 'uuid/v4';
import { generate } from 'generate-password';
import { updateLeague } from '../../actions/leagues';
import { addTeam } from '../../actions/teams';
import { addUser } from '../../actions/users';
import Team from '../../classes/Team';
import User from '../../classes/User';
import UserWidget from '../utils/user/UserWidget';
class ViewLeague extends Component {
state = {
league : null,
isOwner : false,
owner : '',
teams : [],
inviteEmail: ''
};
componentWillMount() {
console.log('mount props', this.props.leagues);
const { leagues, uuid, leagueId, users, teams } = this.props;
if (leagues.length > 0) {
const league = leagues.find(league => league.uuid === leagueId);
const owner = users.find(user => league.leagueManager === user.uuid);
const leagueTeams = teams.filter(team => league.teams.includes(team.uuid));
this.setState({
league,
isOwner: league.leagueManager === uuid,
owner,
teams : leagueTeams
});
}
}
handleUpdate(event, fieldName) {
this.setState({ [ fieldName ]: event.target.value });
}
findUserByEmail(email) {
//Todo if not found here hit server
return this.props.users.find(user => user.email === email);
}
sendInvite = () => {
const { addTeam, addUser, updateLeague } = this.props;
const { league } = this.state;
const newManager = this.findUserByEmail(this.state.inviteEmail);
const newTeamUuid = uuid();
let newLeague = {};
if (newManager) {
const newTeam = new Team('New Team', newManager.uuid, newTeamUuid);
addTeam(newTeam);
} else {
const newPass = generate({
length : 10,
number : true,
uppercase: true,
strict : true
});
const newUserUuid = uuid();
const newUser = new User('', this.state.inviteEmail, newPass, '', '', newUserUuid);
addUser(newUser);
const newTeam = new Team('New Team', newUserUuid, newTeamUuid);
addTeam(newTeam);
newLeague = produce(league, draft => {draft.teams.push(newTeamUuid);});
updateLeague(newLeague);
console.log('invite props', this.props);
console.log('league same', league === newLeague);
}
//Todo handle sending email invite send password and link to new team
console.log('Invite a friend', this.state.inviteEmail);
};
renderInvite() {
const { isOwner, league, teams } = this.state;
if (isOwner) {
if ((league.leagueType === 'draft' && teams.length < 8) || league.leagueType !== 'draft') {
return (
<div>
<p>You have an empty team slot. Invite a fried to join!</p>
<input type="text"
placeholder={'email'}
onChange={() => this.handleUpdate(event, 'inviteEmail')}/>
<button onClick={this.sendInvite}>Invite</button>
</div>
);
}
}
}
renderViewLeague() {
console.log('render props', this.props.leagues);
const { league, owner, teams } = this.state;
const editLink = this.state.isOwner ?
<Link to={`/leagues/edit/${this.props.leagueId}`}>Edit</Link> :
'';
return (
<div>
<h2>{league.leagueName} </h2>
<h3>League Manager: <UserWidget user={owner}/> - {editLink}</h3>
<p>League Type: {league.leagueType}</p>
{this.renderInvite()}
<br/>
<hr/>
<h2>Teams</h2>
<span>{teams.map((team) => (<p key={team.uuid}>{team.teamName}</p>))}</span>
<span>
<h2>Scoring: </h2>
{league.scoring.map((score, index) => (
<p key={index}>{`Round ${index + 1}: ${score} points`}</p>
)
)}
</span>
</div>
);
}
render() {
if (!this.state.league) {
return (
<div>
<h2>No league Found</h2>
</div>
);
} else {
return (
<div>
{this.renderViewLeague()}
</div>
);
}
}
}
export default connect(
({ leagues: { leagues }, teams: { teams }, users: { users }, auth: { uuid } },
{ match: { params: { leagueId } } }) => ({
leagues,
teams,
users,
uuid,
leagueId
}), ({
addTeam : (team) => addTeam(team),
addUser : (user) => addUser(user),
updateLeague: (league) => updateLeague(league)
})
)(ViewLeague);
Reducer
import produce from 'immer';
import {
ADD_LEAGUE,
UPDATE_LEAGUE
} from '../actions/types';
const DEFAULT_LEAGUES = {
leagues: [ {
leagueName : 'Test League',
leagueManager: 'testUser12345',
uuid : 'testLeague12345',
teams : [ 'testTeam12345', 'testTeam23456' ],
scoring : [ 25, 20, 15, 10, 5, -5 ],
leagueType : 'draft'
} ]
};
const leaguesReducer = (state = DEFAULT_LEAGUES, action) =>
produce(state, draft => {
// noinspection FallThroughInSwitchStatementJS
switch (action.type) {
case ADD_LEAGUE:
draft.leagues.push(action.league);
case UPDATE_LEAGUE:
console.log('updating league', action.league);
const { league } = action;
const leagueIndex = draft.leagues.findIndex(fLeague => league.uuid === fLeague.uuid);
draft.leagues.splice(leagueIndex, 1, league);
}
});
export default leaguesReducer;
Any help is greatly appreciated!! More info available if needed
Try adding return; at the end of your case blocks.
You can read more about returning data from producers and see examples of what to do and what not to do here.
I'm building a small app using React, semantic-ui-react, redux-subspace.
I have many different tables and when the user clicks on one of the cells, the value supposed to come out on the console but the result is undefined when it clicked. I'm trying to reuse reducer. Same action with different instances.
I appreciate any comments that guide me to right direction.
PartA.js
This component renders Tables and wrapped with <SubspaceProvider>.
<Segment inverted color='black'>
<h1>Age </h1>
{ this.state.toggle ?
<SubspaceProvider mapState={state => state.withSpouseAge} namespace="withSpouseAge">
<TableForm
headers={spouse_ageHeaders}
rows={spouse_ageData}
namespace={'withSpouseAge'}
/>
</SubspaceProvider> :
<SubspaceProvider mapState={state => state.withoutSpouseAge} namespace="withoutSpouseAge">
<TableForm
headers={withoutSpouse_ageHeader}
rows={withoutSpouse_ageData}
namespace={'withoutSpouseAge'}
/>
</SubspaceProvider> }
TableForm.js
This component return Table with the Data and this is where I want to implement onClick method.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Table } from 'semantic-ui-react';
import { select } from '../actions';
const shortid = require('shortid');
class TableForm extends Component {
constructor(props){
super(props);
this.state = {
activeIndex: 0,
}
this.handleOnClick = this.handleOnClick.bind(this);
this.isCellActive = this.isCellActive.bind(this);
};
isCellActive(index) {
this.setState({ activeIndex: index });
}
handleOnClick(index, point) {
this.isCellActive(index);
this.props.onSelect(point);
};
tableForm = ({ headers, rows }) => {
const customRenderRow = ({ factor, point, point2 }, index ) => ({
key: shortid.generate(),
cells: [
<Table.Cell content={factor || 'N/A'} />,
<Table.Cell
content={point}
active={index === this.state.activeIndex}
textAlign={'center'}
selectable
onClick={() => this.handleOnClick(index, point)}
/>,
<Table.Cell
content={point2}
textAlign={'center'}
selectable
/>
],
});
return (
<Table
size='large'
padded
striped
celled
verticalAlign={'middle'}
headerRow={this.props.headers}
renderBodyRow={customRenderRow}
tableData={this.props.rows}
/>
)
};
render() {
console.log(this.props.withSpouseAgePoint);
const { headers, rows } = this.props;
return (
<div>
{this.tableForm(headers, rows)}
</div>
);
}
};
const mapDispatchToProps = (dispatch) => {
return {
onSelect: (point) => {dispatch(select(point))},
}
}
const mapStateToProps = state => {
return {
withSpouseAgePoint: state.withSpouseAge,
withSpouseLoePoint: state.withSpouseLoe,
}
}
export default connect(mapStateToProps, mapDispatchToProps)(TableForm);
Action
import {
SELECT,
} from './types';
export const select = (points) => ({
type: 'SELECT',
points,
});
Reducer.js
import { SELECT } from '../actions/types';
const INITIAL_STATE = {
point: 0,
};
const selectionReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'SELECT':
return { ...state, point: state.point + action.points };
default:
return state;
}
};
export default selectionReducer;
Reducer index.js
import { createStore, combineReducers } from 'redux';
import { subspace, namespaced } from 'redux-subspace';
import selectionReducer from './selectionReducer';
import toggleReducer from './toggleReducer';
const reducers = combineReducers({
withSpouseAge: namespaced('withSpouseAge')(selectionReducer),
withSpouseLoe: namespaced('withSpouseLoe')(selectionReducer),
withSpouseOlp: namespaced('withSpouseOlp')(selectionReducer),
withSpouseOlp2: namespaced('withSpouseOlp2')(selectionReducer),
withSpouseExp: namespaced('withSpouseExp')(selectionReducer),
withoutSpouseAge: namespaced('withoutSpouseAge')(selectionReducer),
withoutSpouseLoe: namespaced('withoutSpouseLoe')(selectionReducer),
withoutSpouseOlp: namespaced('withoutSpouseOlp')(selectionReducer),
withoutSpouseOlp2: namespaced('withoutSpouseOlp2')(selectionReducer),
withoutSpouseExp: namespaced('withoutSpouseExp')(selectionReducer),
toggle: toggleReducer,
});
Update
I added below TableForm component
const mapDispatchToProps = (dispatch) => {
return {
onSelect: (point) => {dispatch(select(point))},
}
}
const mapStateToProps = state => {
return {
withSpouseAgePoint: state.withSpouseAge,
withSpouseLoePoint: state.withSpouseLoe,
}
}
export default connect(mapStateToProps, mapDispatchToProps)(TableForm);
implement this.props.onSelect(point) on handleOnClick. It still shows me the same result undefined. I checked store states by getState(). consloe.log. I think my implementation of redux-subspace is wrong. I uploaded whole TableForm component and also updated reducer. Please help me out!
update 2
I replaced mapStateToProps and it worked like a magic. Thank you again #JustinTRoss.
but there is another problem, all the states are coming out with the same value when I clicked on the cell.
. my plan is each state has their own value stored.
const mapStateToProps = state => {
return {
withSpouseAgePoint: state,
withoutSpouseAge: state,
}
}
You have already namespaced your component to withSpouseAge and mapped state to state.withSpouseAge in your SubspaceProvider. Thus, you're calling the equivalent of state.withSpouseAge.withSpouseAge (undefined).
Another potential issue is the signature with which you are calling connect. From the snippet you provided, there's no way to be sure of the value of 'select'. Typically, connect is called with 2 functions, often named mapStateToProps and mapDispatchToProps. You are calling connect with a function and an object. Here's an example from http://www.sohamkamani.com/blog/2017/03/31/react-redux-connect-explained/#connect :
import {connect} from 'react-redux'
const TodoItem = ({todo, destroyTodo}) => {
return (
<div>
{todo.text}
<span onClick={destroyTodo}> x </span>
</div>
)
}
const mapStateToProps = state => {
return {
todo : state.todos[0]
}
}
const mapDispatchToProps = dispatch => {
return {
destroyTodo : () => dispatch({
type : 'DESTROY_TODO'
})
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(TodoItem)
Additionally, there's one other issue, although it isn't affecting you yet: You're calling this.tableForm with 2 arguments (headers and rows), while you defined the this.tableForm function to take a single argument and destructure out 'headers' and 'rows' properties.