How can I extend a "connected" component? - reactjs

My situation is, I have the Navigation component, which is the base, and is listening to the Navigations state(Redux). It should be extended to HorizontalNavigation and VerticalNavigation, for easy reusable code in the future.
My problem is, right now I already have the "final" version of Navigation.jsx and I can extend it, as a class, but can't override it's methods. It triggers the super(Navigation) method and not the final one. I need to override the methods in Horizontal or Vertical components.
There is no code erros on console, so it isn't something breaking, but that I don't know how to handle how to extend it.
Navigation.jsx
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import { connect } from 'react-redux';
import { itemAction, stageAction } from 'Store/Actions/Actions';
class Navigation extends Component {
// ACTIONS
leftAction () {
this.onLeftAction();
}
onLeftAction () {}
rightAction () {
this.onRightAction();
}
onRightAction () {}
downAction () {
this.onDownAction();
}
onDownAction () {}
upAction () {
this.onUpAction();
}
onUpAction () {}
// STAGES
nextStage (slug) {
this.goToStage(slug);
}
previousStage (slug) {
this.goToStage(slug);
}
goToStage (slug) {
// Just for illustration purpose
// let { dispatch } = this.props;
// dispatch(stageAction(slug));
}
// ITEMS
nextItem (index) {
this.goToItem(index);
}
previousItem (index) {
this.goToItem(index);
}
goToItem (index) {
// Just for illustration purpose
// let { dispatch } = this.props;
// dispatch(itemAction(index));
}
render () {
return ();
}
}
function mapStateToProps (state, props) {
navigation: state.Navigations[props.slug]
}
export default connect(mapStateToProps)(Navigation);
Horizontal.jsx
import React from 'react';
import Navigation from 'Components/Navigation/Navigation';
class HorizontalNavigation extends Navigation {
onLeftAction (previousItemIndex) {
this.previousItem(previousItemIndex);
}
onRightAction (nextItemIndex) {
this.nextItem(nextItemIndex);
}
onTopAction (slug) {
this.previousStage(slug);
}
onDownAction (slug) {
this.nextStage(slug);
}
}
export default HorizontalNavigation;
The VerticalNavigation would be the opposite. Left and right for stage; up and down for items.
I don't want to reuse the Navigation component each time I could use Horizontal or Vertical, and rewrite the same exact logic over and over again.

I'm using the Higher-Order Component pattern, exporting a function to connect the extended component, eg:
import { connect as reduxConnect } from 'react-redux'
...
export class Navigation extends Component{
...
export function connect(Component){
return reduxConnect(
(state, props)=>({...})
)(Component);
}
export default connect(Navigation)
And in the Horizontal.jsx you could do
import { Navigation, connect } from './Navigation';
class Horizontal extends Navigation{
...
export default connect(Horizontal);
This way, you keep the connect(mapStateToProps) in one place.

This is a fun one. At the bottom of Navigation, you're exporting the connecting component, which in essence is exporting the class created in connect, which is not the same class as Navigation. So, when you extend the default exported class, you're actually extending the connect class. That's a mouthful.
To get this to work, you could also export your class (in addition to export default connect(mapStateToProps)(Navigation); at the bottom:
export class Navigation extends Component {
Then to extend it, you can do:
import { Navigation } from './Navigation';
class Horizontal extends Navigation {
// ...
However, you would also need connect the Horizontal component as well in order to get the right props from redux.
If you don't want to use connect, you could take in props to your Navigation component that changes how those up/down/left/right actions work, then you could create a Horizontal/Vertical component that passes in the right props. Something like:
class Horizontal extends React.Component {
constructor(props, context) {
super(props, context);
this.onUp = this.onUp.bind(this);
this.onDown = this.onDown.bind(this);
this.onLeft = this.onLeft.bind(this);
this.onRight = this.onRight.bind(this);
}
onUp() {}
onDown() {}
onLeft() {}
onRight() {}
render() {
return (
<Navigation onUp={this.onUp} onDown={this.onDown} onLeft={this.onLeft} onRight={this.onRight} />
);
}
);

Related

How to Call a Function From Another Class With React-Redux?

I want to call functions from another class in react.Js and I did it successfully. My problem is that I get Error: You must pass a component to the function returned by connect. Instead received {"refs":{},"updater":{}} if I use react-redux. Here is a basic example of algortihm
import { Component } from "react";
import { GetCityListFromActiveAddressSource } from '../Services/AddressService'
import { connect } from 'react-redux';
class FirstClass extends Component{
constructor(props){
super(props);
}
TestFunction(){
alert("test");
}
render(){
return null
}
}
const mapStateToProps = ({ Modules }) => {
const { GetDynamicMenuList } = Modules;
return { GetDynamicMenuList }
}
const Address = new AddressService();
export default connect(mapStateToProps,{GetCityListFromActiveAddressSource})(Address);
//export default Address;
And My Second class...
import React, { Component } from 'react';
import FirstClass from '../../../ServiceClient/FirstClass';
class SeconClass extends Component {
constructor(props) {
super(props);
}
componentDidMount(){
Address.TestFunction();
}
}
If I don't use connect and react-redux then I can call my function. But I have to use react-redux.
I solved my problem, My problem was that I used export default with const value but react-redux(connect) needs a component or function But I unfortunatelly used const value in my example. That's why I got error. Then I exported my component in connect and I used another export for my const. It works well enough for me.
import React, { Component } from "react";
import { GetCityListFromActiveAddressSource } from '../Services/AddressService'
import { connect } from 'react-redux';
class AddressService extends Component{
constructor(props){
super(props);
}
TestFunction(){
alert("test");
}
render(){
return null
}
}
const mapStateToProps = ({ Modules }) => {
const { GetDynamicMenuList } = Modules;
return { GetDynamicMenuList }
}
export default connect(mapStateToProps,{GetCityListFromActiveAddressSource})
(AddressService);
export const Address = new AddressService();
And I call my function like
import {Address} from '../../../ServiceClient/Address';

Can't extend redux connected class, getting super expression either be null

what i wanna do is to make a component and connect it to redux(to bring some of redux store props to component), then extend this component and make those redux props available to child component as well(cause they have exactly same props). but i faced a problem:
This is parent class
import * as React from 'react'
import { connect } from 'react-redux'
import { View } from 'react-native'
class UnConnectedManageProductBaseScene extends React.Component {
public render(){
return (
<View/>
)
}
}
const mapStateToProps = (state) => {
return {
}
}
const mapDispatchToProps = (dispatch) => {
return {
}
}
export const ManageProductBaseScene = connect(
mapStateToProps,
mapDispatchToProps
)(UnConnectedManageProductBaseScene)
and this is child class
import { ManageProductBaseScene} from '../ManageProductBaseScene'
export class OwnerManageAuctionScene extends ManageProductBaseScene {
constructor(props) {
super()
}
}
when i try to extend OwnerManageAuctionScene from redux connected component (ManageProductBaseScene) i get error: Super expression must either be null or a function
Pass the props as a parameter in super() like this
import { ManageProductBaseScene} from '../ManageProductBaseScene'
export class OwnerManageAuctionScene extends ManageProductBaseScene {
constructor(props) {
super(props);
}
}

Navigation from within a HOC in React Native

I wish to add a check to every page in my app. The check is that if a file exists then pull the user to a page.
I think that a HOC is one way to do this (are there others?)
and I have come up with this
import React from "react";
import { NavigationScreenProp } from "react-navigation";
import RNFS from "react-native-fs";
interface MyComponentProps {
navigation: NavigationScreenProp<any, any>;
}
interface MyComponentState {}
const importFileCheck = WrappedComponent => {
class HOC extends React.Component<MyComponentProps, MyComponentState> {
constructor(props) {
super(props);
this.props.navigation.addListener("didFocus", () => {
RNFS.exists(
".\path\I\care\about.xml"
).then(exists => {
if (exists) {
this.props.navigation.navigate("Export");
}
});
});
}
render() {
return <WrappedComponent {...this.props} />;
}
}
return HOC;
};
export default importFileCheck;
When I run the page I get an error
TypeError: undefined is not an object (evaluating this.props.navigation.addListener)
So I guess that the navigation 'thing' is not being passed through properly
For completion I use the HOC like so
importFileCheck(App)
and App has the navigation stuff already in it, and works without the HOC.
Imports are
"react": "16.6.1",
"react-native": "0.57.7",
"react-navigation": "3.2.0"
Further details for the keen :D
First I make a stack navigator that is all the pages in my app
const appNav = createStackNavigator(
{
Calculator: {
screen: Calculator,
navigationOptions: { title: "Calculator" }
},
// more pages
);
export default createAppContainer(appNav);
In App.tsx
this gets 'wrapped' in other components
const WrappedStack = () => {
return <RootStack screenProps={{ t: i18n.getFixedT() }} />;
};
const ReloadAppOnLanguageChange = translate("translation", {
bindI18n: "languageChanged",
bindStore: false
})(WrappedStack);
class App extends React.Component {
public render() {
return (
<I18nextProvider i18n={i18n}>
<StyleProvider style={getTheme(material)}>
<Provider
ManureStore={ManureStore}
SettingsStore={SettingsStore}
FieldStore={FieldStore}
CalculatorStore={CalculatorStore}
FarmStore={FarmStore}
>
<ReloadAppOnLanguageChange />
</Provider>
</StyleProvider>
</I18nextProvider>
);
}
}
and finally we wrap with my new HOC
export default importFileCheck(App)
It's not easy to see what the error is when you have not provided any examples of how the component is used within react-navigation. Since the issue is related to the navigation prop not being passed it would be helpful to see a more complete example of how the HOC is used within the application, with all the react-navigation details.
That said, maybe you could try using the withNavigation HOC to ensure that the navigation prop is present. It is documented here:
https://reactnavigation.org/docs/en/connecting-navigation-prop.html
Well this defeated me (and the navigation event I wanted to use does not fire when an app returns from the background anyway)
this is my solution
import React, { Component } from "react";
import { NavigationScreenProp } from "react-navigation";
import RNFS from "react-native-fs";
import { AppState } from "react-native";
interface Props {
navigation: NavigationScreenProp<any, any>;
}
interface State {}
export default class ImportFileCheck extends Component<Props, State> {
private _handleAppStateChange = nextAppState => {
if (nextAppState === "active") {
RNFS.exists(
RNFS.DocumentDirectoryPath + "/Inbox/Import.json"
).then(exists => {
if (exists) {
this.props.navigation.navigate("Export");
}
});
}
};
constructor(props) {
super(props);
AppState.addEventListener("change", this._handleAppStateChange);
}
public componentWillUnmount() {
AppState.removeEventListener("change", this._handleAppStateChange);
}
public render() {
return null;
}
}
Then within each page files return statement I slap in a <ImportFileCheck navigation={navigation} />
What a hack!
We can use useNavigation hooks of react-navigation with version 5.x.x or later.
import { useNavigation } from '#react-navigation/native';
then initialize the navigation using useNavigation like
const navigation = useNavigation();
and then use navigation for different navigation actions like.
navigation.navigate('ToScreen');
naviagtion.goBack();
Hope this will help someone.

Creating an HOC for React Component

I'm trying to create an HOC component for a presentational component and having a bit of trouble with the syntax.
Let's say my presentational component is called BlogViewerBase and let's call the HOC component BlogViewerHoc. I want the following:
I want to include some handler functions in my HOC component
I want the HOC component to connect to my Redux store, get state and pass it to the base component
Does this code look right?
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
// Actions
import * as myActions from '../actions/myActions';
// Base component
import BlowViewerBase from '../components/blogViewerBase';
function hocBlogViewer(BlogViewerBase) {
return class BlogViewerHoc extends React.Component {
handlerFunction1() {
// Some logic here
}
handlerFunction2() {
// Some logic here
}
render() {
return <BlogViewerBase {...this.props} />
}
}
}
function mapStateToProps(state) {
return {
var1: state.module.variable1,
var2: state.module.variable2
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(myActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(BlogViewerHoc(BlogViewerBase));
Where I'm struggling is that the examples of HOC components I've come across look more like functions and I think I'm forming mine more like a component so not sure if I'm connecting to the store the right way. Not sure if the mapPropsToState, mapDispatchToState and the handler functions are in the right places.
Define your HOC and then pass your presentational component to it. Also, you can bind an action creator to your props in mapDispatchToProps. Something like:
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { myActionCreator } from 'yourPathToYourActions';
// Actions
import * as myActions from '../actions/myActions';
// Base component
import BlowViewerBase from '../components/blogViewerBase';
function hocBlogViewer(WrappedComponent) {
return class extends React.Component {
handlerFunction1() {
// Some logic here
}
handlerFunction2() {
// Some logic here
}
componentDidMount() {
// I can dispatch this action now
this.props.myActionInProps();
}
render() {
return <WrappedComponent {...this.props} />
}
}
}
function mapStateToProps(state) {
return {
var1: state.module.variable1,
var2: state.module.variable2
};
}
function mapDispatchToProps(dispatch) {
return {
myActionInProps: dispatch(myActionCreator())
};
}
export default connect(mapStateToProps, mapDispatchToProps)(hocBlogViewer(BlowViewerBase));
Your code seems to be OK to me :)
Maybe for simplicity of reading I would do the following adjustments (but this is just my opinion):
const connector = connect(mapStateToProps, mapDispatchToProps)
...
export default function hocBlogViewer(BlogViewerBase) {
return connector(
class BlogViewerHoc extends React.Component {
...

Get redux state on window.scroll

I want to implement pagination. So when a user scrolls down to the bottom I want to make an api call. I see through window.scroll I can find position of scroll and can achieve that. However I want to access redux state to get certian data. Since this event is not bind by any component I won't be able to pass down data. What would be the correct approach in this scenario?
If I want to access redux store through a simple function How can I do that? And on scroll how do I make sure that only request goes through?
You can connect your component that does the scroll. or you can pass props to the component that have the store information. Those are the two recommended ways to reach your store. That being said you can also look at the context
class MyComponent extends React.Component {
someMethod() {
doSomethingWith(this.context.store);
}
render() {
...
}
}
MyComponent.contextTypes = {
store: React.PropTypes.object.isRequired
};
Note: Context is opt-in; you have to specify contextTypes on the component to get it.
Read up on React's Context doc It may not be a complete solution since it could be deprecated in a future version
Edit:
Per the comments with the clarity you provided you can just do this.
import React, { Component } from 'react';
import ReactDOM = from 'react-dom';
import _ from 'lodash';
const defaultOffset = 300;
var topOfElement = function(element) {
if (!element) {
return 0;
}
return element.offsetTop + topOfElement(element.offsetParent);
};
class InfiniteScroll extends Component {
constructor(props) {
super(props);
this.listener = _.throttle(this.scrollListener, 200).bind(this);
}
componentDidMount() {
this.attachScrollListener();
}
componentWillUnmount() {
this.detachScrollListener();
}
scrollListener () {
var el = ReactDOM.findDOMNode(this);
var offset = this.props.offset || defaultOffset;
var scrollTop = (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;
if (topOfElement(el) + el.offsetHeight - scrollTop - window.innerHeight < offset) {
this.props.somethingHere;
}
}
attachScrollListener() {
window.addEventListener('scroll', this.listener);
window.addEventListener('resize', this.listener);
this.listener();
}
detachScrollListener() {
window.removeEventListener('scroll', this.listener);
window.removeEventListener('resize', this.listener);
}
render() {
return (...)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(InfiniteScroll);
I added lodash to the import here so you can throttle the scroll listener function. you only want to call the handler function so many times a second or it can start lagging the page (depending on how heavy the listener function is)
The correct way to access your application state in components is the usage of react-redux and selectors functions.
react-redux provides a function which is called connect. You should use this function to define which values from our state you want to map to the props of the component so these will be available.
The function you need for this mapping is called mapStateToPropswhich returns an object with the values which should be passed to the component.
Also you can be define redux actions which should be made available in the component (e.g. for trigger the load of the next page). The function is called mapDispatchToProps.
Here an example:
import React from 'react';
import { connect } from 'react-redux';
import { getUsersPage } from './selectors';
import { loadUsersPage } from './actions';
class MyComponent extends React.Component {
handleScroll () {
this.props.loadUsersPage({ page: lastPage + 1 });
}
render () {
const users = this.props.users;
// ...
}
}
const mapStateToThis = (state) => {
return {
users: getUsers(state)
};
}
const mapDispatchToProps = (dispatch) => {
return {
loadUsersPage: (payload) => {
dispatch (loadUsersPage(payload));
}
}
};
export default connect()(MyComponent);

Resources