I'm writing a independent modal using React and Redux. I pass from my environment variable if modal is visible and initial position and the rest of the state in redux store.
I've tried using react lifecycle methods to force update my app but nothing seems to work.
This is how I connect my App with store:
render() {
const {
media, initPosition, isMobile, title, isVisible, onClose
} = this.props;
const photos = media.filter(
item => typeof item.video === 'undefined'
);
const videos = media.filter(
item => typeof item.video !== 'undefined'
);
const initState = {
media: {
items: media,
filteredItems: {
all: media,
photos,
videos
},
filter: 'all',
initPosition,
currentPosition: initPosition
},
gallery: {
isMobile,
title
}
};
const store = createStore(
reducer,
initState,
composeEnhancers(applyMiddleware(...middleware))
);
return (
<Provider store={store}>
<App onClose={e => onClose(e)} isVisible={isVisible} />
</Provider>
);
I call my modal like this:
<Gallery
media={videos.concat(photos)}
isMobile={isMobile}
isVisible={show}
onClose={() => this.setState({ show: false })}
initPosition={position}
changePosition={position => this.setState({ position })}
title="Maximus"/>
And this is how I connect it to the state:
function mapStateToProps(state) {
const { media, gallery } = state;
const {
filteredItems, filter, currentPosition, initPosition
} = media;
const { isMobile, title } = gallery;
return {
filteredMedia: filteredItems,
filter,
currentPosition,
initPosition,
isMobile,
title
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
changeMediaProp
}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(
GalleryApp
);
After isVisible is changed nothing seem to work with redux store. It is changing, but the app isn't updating.
When I toggle modal (change isVisible prop), redux state keeps changing, but my app isn't rerendering.
So to sum it up. I change isVisible and initPosition from surrounded application( these props are not stored in store), and when I changed them my component can't react to changes from reducer store.
I was passing multiple stores to my application. I fixed it by saving store in constructor and not creating it multiple times.
let newStore = store;
if (!newStore) {
newStore = createStore(
reducer,
initState,
composeEnhancers(applyMiddleware(...middleware))
);
this.setState({ store: newStore });
}
return (
<Provider store={newStore}>
<App onClose={e => onClose(e)} isVisible={isVisible} />
</Provider>
);
Does anyone have any better solution?
Your code doesn't have enough information to know. Are you using connect and react-redux. Here's a good intro if you need some help.
https://www.sohamkamani.com/blog/2017/03/31/react-redux-connect-explained/
An example component would look like this:
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)
Related
I have built an app on ReactJS 16.8.5 and React-Redux 3.7.2. When the app loads the app mounts, initial store is set and database subscriptions are set up against a Firebase Realtime Database. The app contains a sidebar, header and content section. By profiling the app using React Developer Tools I can see that the Sidebar is being rendered several times - triggering rerender of child components. I have implemented React.memo to avoid rerendring when props change.
From what I can see the props does not change, but the Sidebar still rerenders, which confuses me.
app.js
//Imports etc...
const jsx = (
<React.StrictMode>
<Provider store={store}>
<AppRouter />
</Provider>
</React.StrictMode>
)
let hasRendered = false
const renderApp = () => {
if (!hasRendered) { //make sure app only renders one time
ReactDOM.render(jsx, document.getElementById('app'))
hasRendered = true
}
}
firebase.auth().onAuthStateChanged((user) => {
if (user) {
// Set initial store and db subscriptions
renderApp()
}
})
AppRouter.js
//Imports etc...
const AppRouter = ({}) => {
//...
return (
<React.Fragment>
//uses Router instead of BrowserRouter to use our own history and not the built in one
<Router history={history}>
<div className="myApp">
<Route path="">
<Sidebar />
</Route>
//More routes here...
</div>
</Router>
</React.Fragment>
)
}
//...
export default connect(mapStateToProps, mapDispatchToProps)(AppRouter)
Sidebar.js
//Imports etc...
export const Sidebar = (props) => {
const onRender = (id, phase, actualDuration, baseDuration, startTime, commitTime) => {
if (id !== 'Sidebar') { return }
console.log('Profile', phase, actualDuration)
}
return (
<Profiler id="Sidebar" onRender={onRender}>
<React.Fragment>
{/* Contents of Sidebar */}
</React.Fragment>
</Profiler>
}
const mapStateToProps = (state) => {
console.log('Sidebar mapStateToProps')
return {
//...
}
}
const areEqual = (prevProps, nextProps) => {
const areStatesEqual = _.isEqual(prevProps, nextProps)
console.log('Profile Sidebar isEqual', areStatesEqual)
return areStatesEqual
}
export default React.memo(connect(mapStateToProps, mapDispatchToProps)(Sidebar),areEqual)
Console output
Sidebar mapStateToProps 2
Profile Sidebar mount 225
Sidebar mapStateToProps
Profile Sidebar isEqual true
Sidebar mapStateToProps
Profile Sidebar update 123
Sidebar mapStateToProps 2
Profile Sidebar update 21
Sidebar mapStateToProps
Profile Sidebar update 126
Sidebar mapStateToProps
Profile Sidebar update 166
Sidebar mapStateToProps
Profile Sidebar update 99
Sidebar mapStateToProps
Sidebar mapStateToProps
Sidebar mapStateToProps
Sidebar mapStateToProps
Sidebar mapStateToProps
Sidebar mapStateToProps
Profile Sidebar update 110
Sidebar mapStateToProps
Sidebar mapStateToProps
Sidebar mapStateToProps
Profile Sidebar update 4
Why is the Sidebar rerendering eight times when the props has not changed? One rerender would be expected?
Kind regards /K
As commented; when mapStateToProps returns a new object it will re render the connected component even if no relevant values change.
This is because {} !== {}, an object with same props and values does not equal another object with same props and values because React compares object reference and not the values of the object. That is why you can't change state by mutating it. Mutating changes the values in the object but not the reference to the object.
Your mapStateToProps has to return a new reference at the 2nd level for it to re render with the same values, so {val:1} won't re render but {something:{val:1}} will.
The code below shows how not memoizing the result of mapStateToProps can cause re renders:
const { Provider, connect, useDispatch } = ReactRedux;
const { createStore } = Redux;
const { createSelector } = Reselect;
const { useRef, useEffect, memo } = React;
const state = { val: 1 };
//returning a new state every action but no values
// have been changed
const reducer = () => ({ ...state });
const store = createStore(
reducer,
{ ...state },
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__()
);
const Component = (props) => {
const rendered = useRef(0);
rendered.current++;
return (
<div>
<div>rendered:{rendered.current} times</div>
props:<pre>{JSON.stringify(props)}</pre>
</div>
);
};
const selectVal = (state) => state.val;
const selectMapStateToProps = createSelector(
selectVal,
//will only re create this object when val changes
(val) => console.log('val changed') || { mem: { val } }
);
const memoizedMapStateToProps = selectMapStateToProps;
const mapStateToProps = ({ val }) =>
({ nonMem: { val } }); //re creates props.nonMem every time
const MemoizedConnected = connect(memoizedMapStateToProps)(
Component
);
//this mapStateToProps will create a props of {val:1}
// pure components (returned by connect) will compare each property
// of the prop object and not the props as a whole. Since props.val
// never changed between renders it won't re render
const OneLevelConnect = connect(({ val }) => ({ val }))(
Component
);
const Connected = connect(mapStateToProps)(Component);
const Pure = memo(function Pure() {
//props never change so this will only be rendered once
console.log('props never change so wont re render Pure');
return (
<div>
<Connected />
<MemoizedConnected />
<OneLevelConnect />
</div>
);
});
const App = () => {
const dispatch = useDispatch();
useEffect(
//dispatch an action every second, this will create a new
// state ref but state.val never changes
() => {
setInterval(() => dispatch({ type: 88 }), 1000);
},
[dispatch] //dispatch never changes but linting tools don't know that
);
return <Pure />;
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<div id="root"></div>
The mapStateToProps function can also be optimised more by passing a function that returns a function. This way you can create a memoized selector when the component mounts. This can be used in list items (see code below).
const { useRef, useEffect } = React;
const {
Provider,
useDispatch,
useSelector,
connect,
} = ReactRedux;
const { createStore } = Redux;
const { createSelector } = Reselect;
const state = {
data: [
{
id: 1,
firstName: 'Ben',
lastName: 'Token',
},
{
id: 2,
firstName: 'Susan',
lastName: 'Smith',
},
],
};
//returning a new state every action but no values
// have been changed
const reducer = () => ({ ...state });
const store = createStore(
reducer,
{ ...state },
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__()
);
//selectors
const selectData = (state) => state.data;
const selectPerson = createSelector(
selectData,
(_, id) => id, //pass second argument to select person by id
(people, _id) => people.find(({ id }) => id === _id)
);
//function that will create props for person component
// from person out of state
const asPersonProps = (person) => ({
person: {
fullName: person.firstName + ' ' + person.lastName,
},
});
//in ConnectedPerson all components share this selector
const selectPersonProps = createSelector(
(state, { id }) => selectPerson(state, id),
asPersonProps
);
//in OptimizedConnectedPerson each component has it's own
// selector
const createSelectPersonProps = () =>
createSelector(
(state, { id }) => selectPerson(state, id),
asPersonProps
);
const Person = (props) => {
const rendered = useRef(0);
rendered.current++;
return (
<li>
<div>rendered:{rendered.current} times</div>
props:<pre>{JSON.stringify(props)}</pre>
</li>
);
};
//optimized mapStateToProps
const mapPersonStateToProps = createSelectPersonProps;
const OptimizedConnectedPerson = connect(
mapPersonStateToProps
)(Person);
const ConnectedPerson = connect(selectPersonProps)(Person);
const App = () => {
const dispatch = useDispatch();
const people = useSelector(selectData);
const rendered = useRef(0);
rendered.current++;
useEffect(
//dispatch an action every second, this will create a new
// state ref but state.val never changes
() => {
setInterval(() => dispatch({ type: 88 }), 1000);
},
[dispatch] //dispatch never changes but linting tools don't know that
);
return (
<div>
<h2>app rendered {rendered.current} times</h2>
<h3>Connected person (will re render)</h3>
<ul>
{people.map(({ id }) => (
<ConnectedPerson key={id} id={id} />
))}
</ul>
<h3>
Optimized Connected person (will not re render)
</h3>
<ul>
{people.map(({ id }) => (
<OptimizedConnectedPerson key={id} id={id} />
))}
</ul>
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<div id="root"></div>
Got working App at: https://github.com/BeerDRinker/recompose-ref
Following code(commented part in /src/App.js) works as expected:
class App extends Component {
constructor(props) {
super(props);
this.node = React.createRef();
this.state = {
value: 1
};
}
handleTouchStart = e => {
e.preventDefault();
this.setState({ value: this.state.value + 1 });
};
handleTouchEnd = e => {
e.preventDefault();
this.setState({ value: this.state.value - 1 });
};
componentDidMount() {
this.node.current.ontouchstart = this.handleTouchStart;
this.node.current.ontouchend = this.handleTouchEnd;
}
render() {
return (
<div>
<h3>Value: {this.state.value}</h3>
<button ref={this.node}>Submit</button>
</div>
);
}
}
export default App;
But I need the same functionality by using Recompose. I tried, but got nothing working. My code sample(not commented part in /src/App.js) that don't works:
import React from "react";
import {
compose,
lifecycle,
setDisplayName,
withProps,
withStateHandlers
} from "recompose";
import "./App.css";
const state = {
value: 1
};
const stateHandlers = {
handleTouchStart: value => () => ({
value: value + 1
}),
handleTouchEnd: value => () => ({
value: value - 1
})
};
export const enhance = compose(
setDisplayName("App"),
withProps(props => ({
bookNode: React.createRef()
})),
withStateHandlers(state, stateHandlers),
lifecycle({
componentDidMount() {
this.bookNode.current.ontouchstart =
this.handleTouchStart;
this.bookNode.current.ontouchend = this.handleTouchEnd;
}
})
);
export const App = ({ value, bookNode }) => (
<div>
<h3>Value: {value}</h3>
<button ref={bookNode}>Submit</button>
</div>
);
export default enhance(App);
Just start using recompose, lot of things still magic for me ))
I hope some on can help me, pass several days to solve this problem.
There are problems in composed component.
There's no bookNode and event handlers on this. App is stateless component that doesn't have access to this, bookNode and event handlers are props.
It isn't value that is passed to state handlers, it's state, as the name suggests.
It should be:
const stateHandlers = {
handleTouchStart: state => () => ({
value: state.value + 1
}),
handleTouchEnd: state => () => ({
value: state.value - 1
})
};
export const enhance = compose(
setDisplayName("App"),
withProps(props => ({
bookNode: React.createRef()
})),
withStateHandlers(state, stateHandlers),
lifecycle({
componentDidMount() {
this.props.bookNode.current.ontouchstart = this.props.handleTouchStart;
this.props.bookNode.current.ontouchend = this.props.handleTouchEnd;
}
})
);
export const App = ({ value, bookNode }) => (
<div>
<h3>Value: {value}</h3>
<button ref={bookNode}>Submit</button>
</div>
);
Here's a demo.
Usually there's no reason to access DOM manually to set up events because React handles this. This eliminates the need for a ref and lifecycle hooks:
export const enhance = compose(
setDisplayName("App"),
withStateHandlers(state, stateHandlers)
);
const App = ({ value, handleTouchStart, handleTouchEnd }) => (
<div>
<h3>Value: {value}</h3>
<button onTouchStart={handleTouchStart} onTouchEnd={handleTouchEnd}>Submit</button>
</div>
);
I'm working on this project where the frontend is in React with UIkit for the user interface. The integration between the parts looks poorly implemented. I'm going to explain why. There is a Modal component, something like
export class Modal extends Component {
static getByName = name => UIkit.modal(`[data-modal-name='${name}']`)
static show = name => {
const modal = Modal.getByName(name)
if (modal) modal.show()
}
static hide = name => {
const modal = Modal.getByName(name)
if (modal) modal.hide()
}
render() {
// a modal
}
}
this is used in this way
export const LoginFormModal = props => (
<Modal name="login-form" className="login-form-modal" hideClose>
<LoginForm />
</Modal>
)
and show/hide is called programmatically where needed (even redux's actions)
Modal.hide("login-form")
this is in a Redux action, like this
export const login = credentials => {
return dispatch => {
dispatch(showLoader())
API.authentication.login(
credentials,
response => {
setCurrentUser(
Object.assign({}, response.user, { user_id: response.user.id })
)
Modal.hide("login-form")
dispatch(loginSucceded(response))
dispatch(hideLoader())
dispatch(push("/"))
dispatch(fetchNotificationsCounter())
},
error => {
dispatch(loginFailed(error))
dispatch(hideLoader())
}
)
}
}
This seems to work. Until you leave a component. When you come back to it, the second time the programmatically hide does not work anymore.
Anyone can lead me to how integrate the parts in a more react-appropriate way?
Using the parts of uikit which manipulate the dom (show, hide) is obviously hard to connect with React (and probably you shouldn't), however:
You need to move the call of the functions show and hide inside the Component by passing the bool of the state of the modal (eg. modalopen) . A good hook is the componentWillReceiveProps which can be used to check the previus props
componentWillReceiveProps(nextProps) {
if (nextProps.modalopen !== this.props.modalopen) {
if (nextProps.modalopen) {
getByName(...).show()
} else {
getByName(...).hide()
}
}
}
(this is inside the Modal class)
The thing I don't like and that is definitely not a "React-way" is that the code is mutating state directly from an action creator (!). From React docs:
For example, instead of exposing open() and close() methods on a
Dialog component, pass an isOpen prop to it.
So what if you had one modal that would be controlled by the redux state? Here is a possible implementation:
ModalWindow - will react to state changes and render depending what's in store:
import React from 'react';
import InfoContent from './InfoContent';
import YesOrNoContent from './YesOrNoContent';
import { MODAL_ACTION } from './modal/reducer';
class ModalWindow extends React.Component {
renderModalTitle = () => {
switch (this.props.modalAction) {
case MODAL_ACTION.INFO:
return 'Info';
case MODAL_ACTION.YES_OR_NO:
return 'Are you sure?';
default:
return '';
}
};
renderModalContent = () => {
switch (this.props.modalAction) {
case MODAL_ACTION.INFO:
return <InfoContent />;
case MODAL_ACTION.YES_OR_NO:
return <YesOrNoContent />;
default:
return null;
}
};
render() {
return (
this.props.isModalVisible ?
<div>
<p>{this.renderTitle()}</p>
<div>
{this.renderModalContent()}
</div>
</div>
:
null
);
}
}
export default connect((state) => ({
modalAction: state.modal.modalAction,
isModalVisible: state.modal.isModalVisible,
}))(ModalWindow);
modal reducer it will expose API to show/hide modal window in the application:
export const SHOW_MODAL = 'SHOW_MODAL';
export const HIDE_MODAL = 'HIDE_MODAL';
const INITIAL_STATE = {
isModalVisible: false,
modalAction: '',
};
export default function reducer(state = INITIAL_STATE, action) {
switch (action.type) {
case SHOW_MODAL:
return { ...state, isModalVisible: true, modalAction: action.modalAction };
case HIDE_MODAL:
return { ...state, isModalVisible: false };
default:
return state;
}
}
export const MODAL_ACTION = {
YES_OR_NO: 'YES_OR_NO',
INFO: 'INFO',
};
const showModal = (modalAction) => ({ type: SHOW_MODAL, modalAction });
export const hideModal = () => ({ type: HIDE_MODAL });
export const showInformation = () => showModal(MODAL_ACTION.INFO);
export const askForConfirmation = () => showModal(MODAL_ACTION.YES_OR_NO);
So basically you expose simple API in form of redux action-creators to control the state of your ModalWindow. Which you can later use like:
dispatch(showInformation())
...
dispatch(hideModal())
Of course, there could be more to it like optional configuration that would be passed to action creators or queue for modals.
I use a combination of a hook and a component for this.
Hook:
import { useState } from "react";
import UIkit from "uikit";
export default function useModal() {
const [isOpen, setIsOpen] = useState(false);
const [ref, setRef] = useState(null);
const open = (e) => {
UIkit.modal(ref).show();
setIsOpen(true);
};
const close = (e) => {
UIkit.modal(ref).hide();
UIkit.modal(ref).$destroy(true);
setIsOpen(false);
};
return [setRef, isOpen, open, close];
}
Component:
import React, { forwardRef } from "react";
const Modal = forwardRef(({ children, isOpen, full, close }, ref) => (
<div
ref={ref}
data-uk-modal="container: #root; stack: true; esc-close: false; bg-close: false"
className={`uk-flex-top ${full ? "uk-modal-container" : ""}`}
>
<div className="uk-modal-dialog uk-margin-auto-vertical">
<button
type="button"
className="uk-modal-close-default"
data-uk-icon="close"
onClick={close}
/>
{isOpen && children()}
</div>
</div>
));
export default Modal;
Consumption:
function Demo() {
const [ref, isOpen, open, close] = useModal();
return (
<div>
<button
type="button"
className="uk-button uk-button-default"
onClick={open}
>
upgrade
</button>
<Modal isOpen={isOpen} close={close} ref={ref} full>
{() => (
<div>
<div className="uk-modal-header">
<h2 className="uk-modal-title">title</h2>
</div>
<div className="uk-modal-body">
body
</div>
</div>
)}
</Modal>
</div>
);
}
Read more: https://reactjs.org/docs/integrating-with-other-libraries.html
I have been using react+redux quite while, but could you any one help me the following case, on codepen:
const {createStore } = Redux;
const { Provider, connect } = ReactRedux;
const store = createStore((state={name: 'ron'}, action) => {
switch(action.type) {
case 'changeName': return {name: action.name};
default: return state
}
})
const Person = props => {
const {name, dispatch} = props
console.log(`rendering Person due to name changed to ${name}`)
return (
<div>
<p> My name is {name} </p>
<button onClick={ () => dispatch({type: 'changeName', name: 'ron'}) } > Change to Ron </button>
<button onClick={ () => dispatch({type: 'changeName', name: 'john'}) } > Change to John</button>
</div>
)
}
const App = connect(state=>state)(Person)
ReactDOM.render(
<Provider store={store}><App/></Provider>,
document.getElementById('root')
);
It is simple react app, but I cannot explain:
Initialise redux store with one reducer, and its initValue is {name: 'ron'}
Click Change to ron button, it will dispatch {type: 'changeName', name: 'ron'}
When the reducer get this action, it will generate an brand new state {name: 'ron'}, though the value is same as the original state, but they are different identity and should be the different ones.
The functional component should be re-rendered if the props changed even though the values are the same. So I suppose the render function will be called, and console should output rendering Person due to.... However, it is not happening.
I am wondering why react functional component refuse to render again when the props identity are changed (though the values are the same)
Your connect(state=>state)(Person) I think it's not wrong but it's weird.
According to the documentation https://redux.js.org/docs/basics/UsageWithReact.html you can separate the state and the action dispatcher, commonly naming mapStateToProps and mapDispatchToProps.
So, I propose to you this code:
const mapStateToProps = state => ({
user: state.user
})
const mapDispatchToProps = dispatch => ({
updateName: (name) => dispatch(changeName(name)),
})
class DemoContainer extends Component {
constructor() {
super();
}
render() {
return (
<div>
<p> My name is {this.props.user.name}</p>
<button onClick={ () => this.props.updateName('ron') } > Change to Ron </button>
<button onClick={ () => this.props.updateName('john') } > Change to John</button>
</div>
);
}
}
const Demo = connect(
mapStateToProps,
mapDispatchToProps
)(DemoContainer)
export default Demo
My reducer:
const initialState = { name: 'John'}
const user = (state = initialState, action) => {
switch (action.type) {
case "CHANGE_NAME":
return {
name: action.name
}
default:
return state
}
}
export default user
My action:
export const changeName = ( name ) => ({
type: "CHANGE_NAME",
name,
})
You can check all my code here: https://stackblitz.com/edit/react-tchqrg
I have a class for the component but you can also use a functional component with connect like you do.
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.