Properly using mapActionsToProps in a functional component (react-redux) - reactjs

const updateRouterArr = () => {
if (this.props.router_addr && this.props.router_username && this.props.router_pw) {
this.props.updateRouterArr({
router_addr: this.props.router_addr,
router_username: this.props.router_username,
router_pw: this.props.router_pw
})
} else {
alert('Some fields are missing')
}
}
export const RouterCard = (props) => {
return (
<div>
...
</div>
);
}
const mapStateToProps = state => ({
routers: state.router.routers,
router_addr: state.router.router_addr,
router_username: state.router.router_username,
router_pw: state.router.router_pw,
})
const mapActionsToProps = {
updateRouterElem: updateRouterElem,
updateRouterArr: updateRouterArr,
deleteRouterElem: deleteRouterElem,
}
export default connect(mapStateToProps, mapActionsToProps)(RouterCard);
I am using a functional component with React-Redux. When I tried this, it gave me Parsing error: Identifier 'updateRouterArr' has already been declared
I get why that is happening, but I am not sure about how to properly use React-Redux when using a functional component.
I've declared all actions that I need in
const mapActionsToProps = {
updateRouterElem: updateRouterElem,
updateRouterArr: updateRouterArr,
deleteRouterElem: deleteRouterElem,
}
but it's quite confusing since it does not recognize this.props anymore.
How can I fix this?

You just don't need this. before props. Functional component will receive props as argument.
So code will look like
// Assuming that updateRouterArr is global function
const updateRouterArr = (props) => {
if (props.router_addr && props.router_username && props.router_pw) {
props.updateRouterArr({
router_addr: props.router_addr,
router_username: props.router_username,
router_pw: props.router_pw
})
} else {
alert('Some fields are missing')
}
}
export const RouterCard = (props) => {
// And you can call it from RouterCard component
updateRouterArr(props);
return (
<div>
...
</div>
);
}
const mapStateToProps = state => ({
routers: state.router.routers,
router_addr: state.router.router_addr,
router_username: state.router.router_username,
router_pw: state.router.router_pw,
})
const mapActionsToProps = {
updateRouterElem: updateRouterElem,
updateRouterArr: updateRouterArr,
deleteRouterElem: deleteRouterElem,
}
export default connect(mapStateToProps, mapActionsToProps)(RouterCard);
And simple example

You can try this whether this helps. I have made some changes in mapActionsToProps and I have use object destructuring for getting the props. Props can be accessed by same names.
const updateRouterArr = (
{updateRouterElem, updateRouterArr, deleteRouterElem, routers, router_addr, router_username, router_pw}
) => {
if (router_addr && router_username && router_pw) {
updateRouterArr({
router_addr: router_addr,
router_username: router_username,
router_pw: router_pw
})
} else {
alert('Some fields are missing')
}
}
export const RouterCard = (props) => {
return (
<div>
...
</div>
);
}
const mapStateToProps = state => ({
routers: state.router.routers,
router_addr: state.router.router_addr,
router_username: state.router.router_username,
router_pw: state.router.router_pw,
})
const mapActionsToProps = {
updateRouterElem,
updateRouterArr,
deleteRouterElem,
}
export default connect(mapStateToProps, mapActionsToProps)(RouterCard);

Related

How to pass event handlers to React-node in React-Recompose App

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>
);

lifecycle react , redux, and redux-form apollo-graphql

I have a redux-form component and another container component that load apollo graphql data. Here below just some important parts of code.
FORM COMPONENT:
class Form extends Component {
constructor(props) {
super(props);
setTimeout(function() {
this.executeCode ( 'onChangeInput', { action: 'initForm' , props: this.props, formProps: this.props, formState: this.state });
}.bind(this), 1000);
}
render() {
(...)
}
}
const ComponentWithData = reduxForm({
form: nameForm,
validate,
})(Form);
function mapStateToProps(state, ownProps) {
const log = false;
const statesReturn = { myState: state };
let initialValues;
initialValues = processValues(ownProps, tableCrud, ownProps.data, 'toClient','view' );
statesReturn.initialValues = initialValues ;
return statesReturn;
}
const ComponentWithDataAndState = connect(
mapStateToProps,
null,
)(ComponentWithData);
export default ComponentWithDataAndState;
CONTAINER COMPONENT:
class FormContainer extends Component {
render() {
const { t, ...otherProps} = this.props;
let aElements = [];
let aQlFiltered = {"crud_view_payment":{"table":"payment"}};
const resultCheck = checkLoadCrud (aQlFiltered,this.props);
if (resultCheck.messageError) {
return <MsgError msg={resultCheck.messageError} t={this.props.t} />;
}
if (!resultCheck.globalLoading && !resultCheck.messageError) {
if (this.props['crud_view_'+tableCrud] && this.props['crud_view_'+tableCrud][tableCrud]) {
if (this.props['crud_view_'+tableCrud][tableCrud].deleted) {
aElements.push(<RecordHeadInfo
key="recordhead"
tableCrud={tableCrud}
{...this.props}
data={this.props['crud_view_'+tableCrud][tableCrud]}
/>);
}
}
}
if (!resultCheck.globalLoading && !resultCheck.messageError) {
aElements.push(<Form
crudAction="View"
key="mainform"
id={ this.props.match.params.id }
data={this.props['crud_view_'+tableCrud][tableCrud]}
onSubmit={this.handleSubmit}
containerPropsForm={this.props}
t={this.props.t}
/>);
}
}
return (
<div>
{aElements}
</div>
);
}
}
const withGraphqlandRouter = compose(
graphql(defQls.payment.View, {
name: 'crud_view_payment',
options: props => {
const optionsValues = { variables: {id: props.match.params.id, _qlType: 'View' }};
optionsValues.fetchPolicy = Tables[tableCrud].fetchPolicy ? Tables[tableCrud].fetchPolicy :'network-only';
return optionsValues;
},
}),
)(withRouter(FormContainer));
const mapStateToProps = (state) => {
return {
myState: state,
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators ({ appSubmitStart, appSubmitStop, showConfirm, initialize, dispatch }, dispatch ); // to set this.props.dispatch
};
const withState = connect(
mapStateToProps ,
mapDispatchToProps,
)(withGraphqlandRouter);
const ComponentFull = withState;
export default ComponentFull;
on Form Component I have a setTimeout that execute a code, because I need set disable or hidden field accord to data loaded. I can't do it directly on construct() neither componentDidMount() because if i try to retrieve data from redux: this.props.myState.form ( have no values). it's for that use a timeout, with 1000 is ok, and with 1 milisecond it's ok too, I see this.props.myState.form.myForm.values (with data retrivied from db trough apollo), i prefer 1 milisecond of course because i don't see blink fields that get disabled or dissapears, but i'm not sure that is a good practice, because in a slow computer or slow browser that can produce conflict with the render ?
It's not clear form the lifecycle mixing react, redux, apollo and redux-form; anyone has idea how i can order better my ideas to write better code here?

React/Redux Why does specific component update, when its sibling’s child component updates, though its state doesn’t change

Update
The sidedrawers state is apparently different, but value does not change...
Details:
There is a layout component, which takes in routes from react router as children.
Inside the layout component code, two child components are rendered, Toolbar, and sidedrawer, and a main section that contains this.props.children.
One of the routes renders a component called page. Page renders another component called graphContainer, and passes it a click event, which is applied to the graphContainer’s button that it renders.
How it works is, I grab the first eight graphs and show 4 of them. When the button is clicked, it decides to either show the next 4 or grab the next eight.
This whole thing uses redux. There’s a page state, authentication state, navigation state, and a graph state. The only partial state changing when the button is clicked, is the graphs.
However, both the GraphContainer updates along with the sidedrawer component. As far as I can tell, nothing in the sidedrawer component is changing, so it should not trigger an update.
In the redux page for navigation state, the switch hits the default, which just returns state.
The graph redux portion works just fine, updates accordingly.
My workaround was to implement a dontUpdate prop in the navigation reducer state. And then use shouldComponentUpdate to check that prop, because the shallow check that was done by default, say with pureComponent, was seeing a different state or prop.
tl;dr: Any ideas why the sidedrawer component keeps updating, even though, as far as I can tell, there’s no prop or state change?
Reducers
const graphReducer = (state = initialState, action) => {
...
case SHOW_NEXTFOUR:
console.log('SHOW NEXT FOUR', state);
return {
...state,
ttlShown: action.ttlShown
};
default:
return state;
}
};
const navReducer = (state = initialState, action) => {
...
default:
return {...state, dontUpdate: true};
}
};
Layout Component
class Layout extends Component {
...
handleSideBarOpen = () => {
this.props.onSidebarToggle();
}
render () {
return (
<Aux>
<Toolbar
isAuth={this.props.isAuthenticated}
drawerToggleClicked={this.handleSideBarOpen}
/>
<SideDrawer
open={this.props.sidebarOpen}
closed={this.props.onSidebarToggle}
/>
<main className={classes.Content}>
{this.props.children}
</main>
</Aux>
)
}
}
const mapStateToProps = ({ navigation, auth }) => {
const { sidebarOpen } = navigation;
const { token } = auth;
return {
sidebarOpen,
isAuthenticated: token !== null
};
};
const mapDispatchToProps = {
onSidebarToggle, getNavTree
};
export default connect(
mapStateToProps, mapDispatchToProps
)(Layout);
Sidedrawer Component
class sideDrawer extends Component {
state = {
popupMenuOpen: false
}
shouldComponentUpdate ( nextProps, nextState ) {
if(nextProps.dontUpdate)
return false;
else return true;
}
…
render() {
…
let navitems = [];
if(this.props.navData && !this.props.error) {
navitems = (
<NavigationItems
showClients={this.props.showClientsBtn}
navData={this.props.navData}
curClientid={this.props.curClientid}
curSiteid={this.props.curSiteid}
curDashid={this.props.curDashid}
curPageid={this.props.curPageid}
closeSidebar={this.props.closed}
onPageClick={this.handlePageClick}
onCSDClick={this.handleOpenPopupMenu}
/>
);
} else
navitems = <p>Problem Loading Tree</p>;
return (
<Aux>
<div className={attachedClasses.join(' ')}>
<div className={classes.Logo}>
<div className={classes.CloseWrapper}>
<Chip onClick={this.props.closed} className={classes.CloseChip}>X</Chip>
</div>
<div className={classes.CrumbWrapper}>
<Breadcrumbs
backBtn={this.handleBackClick}
handleCrumbClick={this.handleCrumbClick}
breadcrumbs={this.props.breadcrumbs}
/>
</div>
</div>
<nav>
{navitems}
<Popover
style={{width: "90%"}}
open={this.state.popupMenuOpen}
anchorEl={this.state.anchorEl}
anchorOrigin={{horizontal: 'middle', vertical: 'bottom'}}
targetOrigin={{horizontal: 'middle', vertical: 'top'}}
onRequestClose={this.handleClosePopupMenu}
>
<Menu
style={{width: "87%"}}>
{MIs}
</Menu>
</Popover>
</nav>
</div>
</Aux>
);
}
};
const mapStateToProps = ({ navigation }) => {
const { dontUpdate, clientid, breadcrumbs,currentPage, selectedClient, selectedSite, selectedDash, selectedPage, navigationData, sidebarOpen, navError } = navigation;
...
}
return {
dontUpdate,
clientid,
showClientsBtn,
navData,
curClientid,
curSiteid,
curDashid,
curPageid,
parentPageid,
sidebarOpen,
navError,
breadcrumbs,
currentPage
};
};
const mapDispatchToProps = {
getNavTree,
onPageSelected,
onSwitchCSD,
onPageRoute
};
export default withRouter(connect(
mapStateToProps, mapDispatchToProps
)(sideDrawer));
Page Component
class Page extends Component {
componentWillMount () {
this.props.getCurPage();
}
render () {
let content = null;
if(this.props.location.state && this.props.location.state.currentPage)
content = (<GraphContainer pageid={this.props.location.state.currentPage} />);
return this.props.location.state && this.props.location.state.currentPage ? (
<Aux>
<p>A PAGE!</p>
{content}
</Aux>
) : (<Redirect to="/" />);
}
}
const mapStateToProps = ({pages}) => {
const { clientid, curPage } = pages;
return {
clientid, curPage
};
};
const mapDispatchToProps = {
getSelectedPage, getCurPage
};
export default connect(
mapStateToProps, mapDispatchToProps
)(Page);
Graph Container
class GraphsContainer extends Component {
componentWillReceiveProps(newProps) {
if(this.props.pageid !== newProps.pageid)
this.props.getFirstEight(newProps.pageid);
}
componentDidMount() {
if(this.props.pageid)
this.props.getFirstEight(this.props.pageid);
}
handleNextClick = (event) => {
event.preventDefault();
this.props.getNextEight(this.props.pageid, this.props.lastNum, this.props.ttlShown);
}
render() {
let graphcards = null;
let disableNext = null;
if (this.props.lastNum >= this.props.ttl)
disableNext = true;
if(this.props.graphs && this.props.graphs.length > 0) {
graphcards = ...
}
return (
<div className={classes.Shell}>
{graphcards}
{this.props.lastNum < this.props.ttl ? (
<div className={classes.NavBtns}>
<RaisedButton disabled={disableNext} onClick={this.handleNextClick}>{'V'}</RaisedButton>
</div>
):null}
</div>
);
}
}
const mapStateToProps = ({pageGraphs}) => {
const { graphs, ttl, lastNum, ttlShown } = pageGraphs;
return {
graphs, ttl, lastNum, ttlShown
};
};
const mapDispatchToProps = {
getFirstEight, getNextEight
};
export default connect(
mapStateToProps, mapDispatchToProps
)(GraphsContainer);
Actions
export const getFirstEight = (pageid) => {
let str = ...;
return (dispatch) => {
axios.get( str )
.then( response => {
let data = {};
let graphs;
let ttl;
let newLastNum = 0;
if((typeof response.data !== 'undefined') && (response.data !== null)) {
data = {...response.data};
ttl = data.total;
if(ttl <= 8) {
graphs = [...data.graphs];
newLastNum = ttl;
} else {
graphs = [...data.graphs].slice(0,8);
newLastNum = 8;
}
}
dispatch({type: GET_FIRSTEIGHT, payload: {ttl,graphs, lastNum:newLastNum}});
} )
.catch( error => {
console.log('ERROR FETCHING NAV TREE', error);
dispatch({type: GET_FIRSTEIGHT, payload: {}});
} );
};
};
export const getNextEight = (pageid, lastNum, ttlShown) => {
let str = ...;
let newLastNum = 0;
return (dispatch) => {
if(ttlShown < lastNum) {
dispatch({type: SHOW_NEXTFOUR, ttlShown: ttlShown+4});
} else {
axios.get( str )
.then( response => {
// console.log('[RESPONSE]', response);
let data = {};
let graphs;
let ttl;
if((typeof response.data !== 'undefined') && (response.data !== null)) {
data = {...response.data};
ttl = data.total;
if(ttl <= (lastNum+8)) {
graphs = [...data.graphs].slice(lastNum);
newLastNum = ttl;
} else {
graphs = [...data.graphs].filter((el,index) => {
return (index > (lastNum-1)) && (index < (lastNum+8));
});
newLastNum = lastNum+8;
}
}
dispatch({type: GET_NEXTEIGHT, payload: {ttl,graphs, lastNum:newLastNum, ttlShown: ttlShown+4}});
} )
.catch( error => {
console.log('ERROR FETCHING NAV TREE', error);
dispatch({type: GET_NEXTEIGHT, payload: {}});
} );
}
};
};

How to reuse reducer with same action using redux-subspace

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.

Context in "stateless" component?

I have the following code in a component and I would like a stateless component to access this part of code:
Main component:
function createApp(store, communityIds) {
const App = React.createClass({
childContextTypes: {
localizedString: React.PropTypes.func,
},
getChildContext: function() {
return {
localizedString: function(key, fallback) {
return getKey(key, fallback);
},
};
},
render: function() {
return (
<Provider store={store}>
<Client communityIds={communityIds}/>
</Provider>
);
},
});
return <App/>;
}
Stateless:
export default () => (dispatch, getState) => {
const state = getState();
const token = state.user.get('token');
if (!token) {
throw new Error('test'); // this.context.localizedString does not work
}
}
What you have provided under your definition of "Stateless:" function is not what a Stateless function is. You have provided your action creator as a thunk. I assume you wanted to insert the code for your Client component instead. To access context in a stateless component, your Client component would do something like this (which is documented here)
const Client = (props, context) => {
return <div >{context.localizedString("someKey", "someFallback")} </div>
}
Client.contextTypes = {
localizedString: React.PropTypes.func
}
export default Client
I had the same question. The modern way (in 2019) is to use hook useContext(contextName). Docs: https://reactjs.org/docs/hooks-reference.html#usecontext
const dumbComp = (props) => {
const context = useContext(contextName);
return(
<div>
...
</div>
);
}
Use second parameter of stateless component
const MyStatelessComponent = (props, context) => {
const onGoButtonClick = () => {
context.router.push('https://www.google.co.in');
};
return(
<div>
<button onClick={() => onButtonClick()}>
{props.buttonName}
</button>
</div>
);
}
MyStatelessComponent.propTypes = {
buttonName: PropTypes.string.isRequired,
};
MyStatelessComponent.contextTypes = {
router: React.PropTypes.object.isRequired,
};
export default MyStatelessComponent;
Another solution is a self invoking function:
export default (Component=>(
Component.contextTypes = {
localizedString: React.PropTypes.func
}) && Component
)((props, context)=>{
return <div>{context.localizedString("someKey", "someFallback")}</div>
})
Or if you define the contextTypes separately to reuse it, you could do:
//addContextTypes.js
export default function(Component){
return (Component.contextTypes = {
localizedString: React.PropTypes.func
}) && Component
}
//component.jsx
import addContextTypes from './addContextTypes'
export default addContextTypes((props, context)=>{
return <div>{context.localizedString("someKey", "someFallback")}</div>
})
I had the same question, but was on Expo SDK 32, meaning I don't have access to hooks.
Here's how I achieved it:
import { reduxForm } from 'redux-form'
import { ReduxFormContext } from 'redux-form/lib/ReduxFormContext'
const ShowFormName = () => (
<ReduxFormContext.Consumer>
{
({ form }) => <Text>{form}</Text>
}
</ReduxFormContext.Consumer>
)
export default reduxForm({ form: 'formName' })(ShowFormName)

Resources