I'm new to reactjs reselect, the problem is when I Perf.printWasted, it always shows "Content Single > Connect(Single Entries Index)" with only 1 instance than renders xx times
here's my current code
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { shape } from 'prop-types';
import Entry from '/components/entries/Entry';
class Entries extends Component {
static displayName = 'Single Entries Index';
static propTypes = { entries: shape() };
shouldComponentUpdate(nextProps) {
if (this.props.entries.size !== nextProps.entries.size) {
return true;
}
return false;
}
render() {
const { entries } = this.props;
return (
<ul className="entries">
{entries.map(entry => (
<Entry
key={entry.get('id')}
id={entry.get('id')}
/>
))}
</ul>
);
}
};
const makeGetEntries = createSelector(
state => state.data,
data => data.get('entries')
);
const mapStateToProps = (state) => {
return {
entries: makeGetEntries(state)
};
};
export default connect(mapStateToProps)(Entries);
Working solution using createSelectorCreator as suggested by #Logar
const createDeepEqualSelector = createSelectorCreator(
defaultMemoize,
isEqual
);
const makeGetEntries = createDeepEqualSelector(
state => state.data,
data => data.get('entries')
);
const mapStateToProps = (state) => {
return { entries: makeGetEntries(state) };
};
export default connect(mapStateToProps)(Entries);
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 am new to React's context API and hooks for function components. I am trying to pass state to a child component, ActivityTable.js. I wrapped the provider around the app (App.js), however state properties are still undefined in ActivityTable.js -- TypeError: Cannot read property 'id' of undefined.
Any guidance would be appreciated.
App.js
import ActivityState from "./context/activities/ActivityState";
const App = () => {
return (
<StylesProvider injectFirst>
<ContactState>
<ActivityState>
...
</ActivityState>
</ContactState>
</StylesProvider>
);
};
export default App;
ActivityState.js
import React, { useReducer } from 'react';
import ActivityContext from './ActivityContext';
import ActivityReducer from './ActivityReducer';
import { ADD_ACTIVITY, DELETE_ACTIVITY, SET_CURRENT_ACTIVITY } from '../types';
const ActivityState = props => {
const initialState = {
activities: [
{
id: 1,
activity_description: "a desc",
activity_name: "a",
},
{
id: 2,
activity_description: "b desc",
activity_name: "b",
},
{
id: 3,
activity_description: "c desc",
activity_name: "c",
}
]
};
const [state, dispatch] = useReducer(ActivityReducer, initialState);
const deleteActivity = id => {
dispatch({ type: DELETE_ACTIVITY, payload: id });
};
const setCurrentActivity = activity => {
dispatch({ type: SET_CURRENT_ACTIVITY, payload: activity });
};
return (
<ActivityContext.Provider
value={{
activities: state.activities,
deleteActivity,
setCurrentActivity
}}>
{ props.children }
</ActivityContext.Provider>
);
}
export default ActivityState;
ActivityContext.js
import { createContext } from "react";
const ActivityContext = createContext(null);
export default ActivityContext;
ActivityReducer.js
import { DELETE_ACTIVITY, SET_CURRENT_ACTIVITY } from '../types';
export default (state, action) => {
switch (action.type) {
case DELETE_ACTIVITY:
return {
...state,
activities: state.activities.filter(
activity => activity.id !== action.payload
)
};
case SET_CURRENT_ACTIVITY:
return {
...state,
current: action.payload
};
default:
return state;
}
};
ActivityView.js
import React, { useContext } from "react";
import ActivityContext from '../../context/activities/ActivityContext';
import ActivityTable from './ActivityTable';
const Activities = () => {
const activityContext = useContext(ActivityContext);
const { activities } = activityContext;
console.log('activities: ', activities);
return (
<div>
<ActivityTable/>
</div>
);
}
export default Activities;
ActivityTable.js
import React, { useContext, useState } from "react";
import ActivityContext from "../../context/activities/ActivityContext";
const ActivityTable = ({ activity }) => { //activity is undefined here
const activityContext = useContext(ActivityContext);
const { activities } = activityContext;
const { id, activity_name, activity_desc } = activity; //undefined
return (
<div>
<tr>
<td>{id}</td>
<td>{activity_name}</td>
<td>{activity_desc}</td>
</tr>
</div>
);
};
export default ActivityTable;
It looks like you're using activity as a prop inside ActivityTable, but never actually supplying that prop.
<ActivityTable activity={foo} />
I can't tell what data you're trying to pass to the table. You're importing the context successfully in both components, but never using the context data.
I've asked a similar-ish question here before, however my code has changed quite a bit and I can not figure this out. I am certain it's an issue with what I am passing to my action/reducer. I would seriously appreciate it if someone could explain what I am doing wrong here. I really want to get this, just having a hard time with it.
actions.js
import { ADD_TODO, REMOVE_TODO } from '../constants/action-types';
export const addTodo = (todo) => (
{
type: ADD_TODO,
payload: todo
}
);
export const removeTodo = (id) => (
{
type: REMOVE_TODO,
payload: id
}
)
reducers.js
import { ADD_TODO, REMOVE_TODO, ADD_OPTIONS } from '../constants/action-types';
import uuidv1 from 'uuid';
const initialState = {
todos: []
};
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_TODO:
return {
...state,
todos: [...state.todos,
{
title: action.payload.inputValue,
id: uuidv1(),
createdAt: Date(),
priority: '',
deadline: '',
isClicked: false
}]
}
case REMOVE_TODO:
return {
...state,
todos: [...state.todos.filter(todo => todo.id !== action.payload)]
}
case ADD_OPTIONS:
return {
...state,
todos: [...state.todos,
{
isClicked: false
}]
}
default:
return state;
}
}
export default rootReducer;
TodoList.js
import React, { Component } from 'react';
import TodoItem from './TodoItem';
import { removeTodo } from '../actions';
import { connect } from 'react-redux';
const mapDispatchToProps = dispatch => {
return {
removeTodo: id => dispatch(removeTodo(id))
};
};
const mapStateToProps = state => {
return {todos: [...state.todos]};
};
class List extends Component {
render() {
const mappedTodos = this.props.todos.map((todo, index) => (
<TodoItem
title={todo.title}
key={index}
removeTodo={this.props.removeTodo}
/>
));
return (
mappedTodos
);
}
}
const TodoList = connect(mapStateToProps, mapDispatchToProps) (List)
export default TodoList;
TodoItem.js
import React, { Component } from 'react';
import uuid from 'uuid';
import '../../css/Todo.css';
class TodoItem extends Component {
render() {
const todoId = uuid();
return (
<div id={todoId}>
{this.props.title}
<button onClick={this.props.removeTodo}>X</button>
</div>
);
}
}
export default TodoItem;
You need to wrap your remove handler in an expression that can be evaluated at "click time" and use the todo id from the closure:
class TodoItem extends Component {
render() {
const todoId = uuid();
return (
<div id={todoId}>
{this.props.title}
<button onClick={this.props.removeTodo}>X</button>
</div>
);
}
}
Should be something like...
class TodoItem extends Component {
render() {
const todoId = uuid();
return (
<div id={todoId}>
{this.props.title}
<button onClick={() => this.props.removeTodo(todoId)}>X</button>
</div>
);
}
}
Along the lines of what #The Dembinski was saying, it works when I change my TodoList component to look like this:
import React, { Component } from 'react';
import TodoItem from './TodoItem';
import { removeTodo } from '../actions';
import { connect } from 'react-redux';
const mapDispatchToProps = dispatch => {
return {
removeTodo: id => dispatch(removeTodo(id))
};
};
const mapStateToProps = state => {
return {todos: [...state.todos]};
};
class List extends Component {
render() {
const mappedTodos = this.props.todos.map((todo, index) => (
<TodoItem
title={todo.title}
key={index}
removeTodo={() => this.props.removeTodo(todo.id)}
/>
));
return (
mappedTodos
);
}
}
const TodoList = connect(mapStateToProps, mapDispatchToProps) (List)
export default TodoList;
Changing my removeTodo prop in the map here DID fix the issue and now deletes properly. However, if anyone could help me understand this better either by further discussion, or just by pointing my in the right direction as to what I should be researching. Would be greatly appreciated. I'm not after answers, I'm after learning.
I am trying to get a list of names stored in firebase to save to the redux store when the component loads. This list then gets sent to a dropdown component as props which are rendered as selectable options in the dropdown. For some reason I am getting 'dispatch is not defined' and I am not sure how to fix.
This is my code:
//store
import * as redux from 'redux';
import thunk from 'redux-thunk';
import {userReducer, exampleReducer, namesReducer} from 'reducers';
export var configure = (initialState = {}) => {
const reducer = redux.combineReducers({
user: userReducer,
example: exampleReducer,
names: namesReducer
})
var store = redux.createStore(reducer, initialState, redux.compose(
redux.applyMiddleware(thunk),
window.devToolsExtension ? window.devToolsExtension() : f => f
));
return store;
};
//reducers
export var namesReducer = (state = [], action) => {
switch (action.type) {
case 'GET_NAMES':
return [
action.names
]
default:
return state;
}
}
//actions
export var getNames = (names) => {
return {
type: 'GET_NAMES',
names
}
};
export var startGetNames = () => {
console.log("action started")
return (dispatch) => {
var nameRef = firebase.database().ref().child('names');
return nameRef.once('value').then((snapshot) => {
var data = snapshot.val();
_.map(data, function(name) {return finalArr.push(
{
display: `${name.first_name} ${name.last_name}`,
value: name.id
}
)});
dispatch(getNames(finalArr));
})
}
}
//component
import _ from 'underscore';
import React from 'react';
import { render } from "react-dom";
import {connect} from 'react-redux';
import Modal from 'react-responsive-modal';
var actions = require('actions');
var firebase = require('firebase/app');
require('firebase/auth');
require('firebase/database');
//components
import roomBookingForm from 'roomBookingForm';
import PanelHeader from 'PanelHeader';
import PanelItem from 'PanelItem';
import Table from 'Table';
import TableRow from 'TableRow';
import TableHeaderCell from 'TableHeaderCell';
import TextInput from 'TextInput';
import Dropdown from 'Dropdown';
class roomBooking extends React.Component {
constructor(props) {
super(props);
this.state = {
pageTitle: "Appointment Creation",
openForm: false
}
}
componentWillMount() {
this.props.clinicians
}
onOpenModal = () => {
this.setState({openForm: true});
}
modalClose = () => {
this.setState({ openForm: false });
};
render() {
return (
<div className="main-container">
<div className="options-menu">
<PanelHeader >
Options
</PanelHeader>
<PanelItem onClick={this.onOpenModal} propClassLeft="left-item" propClassRight="right-item">Create Appointment</PanelItem>
</div>
<div>
<Table className="display-table">
<TableRow className="display-table-head">
<TableHeaderCell className="name" displayText="Name" />
</TableRow>
</Table>
</div>
<roomBookingForm open={this.state.openForm} onClose={this.modalClose} options={this.props.names} />
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
names : dispatch(actions.startGetNames())
}
export default connect()(roomBooking);
You have two correction in your code.
1.
You need to pass mapDispatchToProps in the connect call.
const mapDispatchToProps = (dispatch) => {
names : dispatch(actions.startGetNames())
}
export default connect(null,mapDispatchToProps)(roomBooking);
2.To call asynchronous action method with react-redux,the right signature is :
export var startGetNames = () => (dispatch) => {
return (dispatch) => {
//code
}
}
There are few issues with your code:
finalArr is not defined before using it.
dispatch does not work like this. It needs to be called like store.dispatch({ACTION}). So, you'll need to import store.
you should pass mapDisPatchToProps to connect function:
const mapDispatchToProps = (dispatch) => {
names : dispatch(actions.startGetNames())
}
export default connect(function(){},mapDispatchToProps)(roomBooking);
There like hundreds of questions about this topic but nothing worked for me.
As you can see in the picture above that the state is obviously updating by this.props.getSubcontractors(); in
containers/SubcontractorListPage.js componentWillMount() function. But the viewComponent SubcontractorList.js does not rerender on Props change. There is a blank page.
containers/SubcontractorListPage.js
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import SubcontractorList from
'../components/viewComponents/SubcontractorList/SubcontractorList';
import * as SubcontractorActions from '../actions/subcontractor';
class SubcontractorListPage extends Component {
static propTypes = {
getSubcontractors: PropTypes.func.isRequired,
subcontractor: PropTypes.object.isRequired,
};
componentWillMount() {
console.log(this.props);
this.props.getSubcontractors();
}
render() {
return (
<SubcontractorList subcontractors=
{this.props.subcontractor.subcontractors} />
);
}
}
export default connect(
state => ({ subcontractor: state.subcontractor }),
dispatch => bindActionCreators(SubcontractorActions, dispatch)
)(SubcontractorListPage);
components/viewComponents/SubcontractorList/SubcontractorList.js
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import styles from './SubcontractorList.scss';
class SubcontractorList extends Component {
static propTypes = {
subcontractors: PropTypes.array.isRequired,
};
render() {
const subcontractors = this.props.subcontractors;
let display = <div />;
console.log(subcontractors);
display = (
<div className={styles.Breadcrumb}>
{subcontractors.map((item) =>
<div className={styles.Item} key={item.Id}>
{item.Name}lol</div>
)}
</div>
);
return display;
}
}
export default SubcontractorList;
actions/subcontractor.js
export const SUBCONTRACTORS_GET = 'SUBCONTRACTORS_GET';
export const SUBCONTRACTOR_GET = 'SUBCONTRACTOR_GET';
export const SUBCONTRACTOR_ADD = 'SUBCONTRACTOR_ADD';
export const SUBCONTRACTOR_EDIT = 'SUBCONTRACTOR_EDIT';
export const SUBCONTRACTOR_DELETE = 'SUBCONTRACTOR_DELETE';
export function getSubcontractors() {
return {
type: SUBCONTRACTORS_GET
};
}
export function getSubcontractor() {
return {
type: SUBCONTRACTOR_GET
};
}
reducers/subcontractor.js
import { SUBCONTRACTORS_GET, SUBCONTRACTOR_GET } from
'../actions/subcontractor';
import getTable from '../utils/dbHelper/getTable';
const initialState = {
subcontractors: [],
subcontractor: {}
};
export type subcontractorStateType = {
subcontractors: [],
subcontractor: {}
};
type actionType = {
type: string
};
export default function subcontractor(state: {} = initialState,
action: actionType) {
switch (action.type) {
case SUBCONTRACTORS_GET:
return {
...state,
subcontractors: getTable('SUBCONTRACTOR')
};
case SUBCONTRACTOR_GET:
console.log('SUBCONTRACTORS_GET');
break;
default:
return state;
}
}
How to resolve this?
Update
const sqlite3 = require('sqlite3').verbose();
const dbSrc = '../testdb';
export default function getTable(tablename) {
const db = new sqlite3.Database(dbSrc);
const queries = [];
db.each(`SELECT * FROM ${tablename}`, (err, row) => {
queries.push(row);
console.log(row);
});
db.close();
return queries;
}