higher order component can't see props - javascript

I'm using react with react-native and redux. The error comes to the component from the redux store. After that, i received: Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
What is wrong with this? why hoc can't see the props?
My component:
import React, { Component } from 'react';
import withHandleError from './withHandleError';
class SendScreen extends Component {
render() {
const { error } = this.props;
return (
<div> Test </div>
)
}
};
const mapStateToProps = ({ppm}) => ({
error: ppm.error
})
export default withHandleError(connect(mapStateToProps)(SendScreen));
And HoC:
import React, { Component } from 'react';
import { ErrorScreen } from '../../ErrorScreen';
import { View } from 'react-native';
export default Cmp => {
return class extends Component {
render() {
const {error, ...rest } = this.props;
console.log(error) //// undefined....
if (error) {
return <ErrorScreen />
}
return <Cmp { ...rest } />
}
}
}

The order is which you call the HOCs matters when you want to access props supplied by one in another. Re-ordering your connect and withHandleError HOC will work
import React, { Component } from 'react';
import withHandleError from './withHandleError';
class SendScreen extends Component {
render() {
const { error } = this.props;
return (
<div> Test </div>
)
}
};
const mapStateToProps = ({ppm}) => ({
error: ppm.error
})
export default connect(mapStateToProps)(withHandleError(SendScreen));

Related

Implementing Higher-Order Components in React - Redux

I am building an app with react / redux for managing Collection of Electronic equipment (=donations). I have several routes that their functionality - is similiar - fetching entity (it could be volunteer, donor etc) data and show it in a table.
the volunteer route:
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { requestVolunteerData } from '../actions/entitiesAction';
import { volenteerColumns as columns } from '../utils/entitiesColumns/volenteerColumns';
import '../container/App.css';
import Table from '../components/Table/Table';
import Loading from '../components/Loading/Loading';
const mapStateToProps = state => {
return {
entities: state.requestEntitiesReducer.entities,
isPending: state.requestEntitiesReducer.isPending,
error: state.requestEntitiesReducer.error
}
}
const mapDispatchToProps = dispatch => {
return {
onRequestEntities: () => dispatch(requestVolunteerData())
}
}
class Volenteer extends Component{
componentDidMount () {
this.props.onRequestEntities();
}
render () {
const { entities, isPending} = this.props;
return isPending ?
<Loading />
:
(
<div className='tc'>
<h1 className='f2'>רשימת מתנדבים</h1>
<Table data={ entities } columns={ columns } />
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Volenteer);
and a consumer route look like this:
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { requestConsumerData } from '../actions/entitiesAction';
import { consumerColumns as columns } from '../utils/entitiesColumns/consumerColumns';
import '../container/App.css';
import Table from '../components/Table/Table';
import Loading from '../components/Loading/Loading';
const mapStateToProps = state => {
return {
entities: state.requestEntitiesReducer.entities,
isPending: state.requestEntitiesReducer.isPending,
error: state.requestEntitiesReducer.error
}
}
const mapDispatchToProps = dispatch => {
return {
onRequestEntities: () => dispatch(requestConsumerData())
}
}
class Consumer extends Component{
componentDidMount () {
this.props.onRequestEntities();
}
render () {
const { entities, isPending} = this.props;
return isPending ?
<Loading />
:
(
<div className='tc'>
<h1 className='f2'>רשימת נזקקים</h1>
<Table data={ entities } columns={ columns }/>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Consumer);
As you can see, they both have the same logic and the differences are:
the action
the Entity name for the h1 tag
the columns object
the data of course
so I tried to implement an HOC which look like this:
import React, {Component} from 'react';
import { connect } from 'react-redux';
import '../container/App.css';
import Table from '../Table/Table';
import Loading from '../Loading/Loading';
export default function WithEntity (EntityComponent, action, columns, name) {
const mapStateToProps = state => {
return {
isPending: state.requestEntitiesReducer.isPending,
entities: state.requestEntitiesReducer.entities,
error: state.requestEntitiesReducer.error
}
}
const mapDispatchToProps = dispatch => {
return {
onRequestEntities: () => dispatch(action)
}
}
class extends Component {
componentDidMount () {
this.props.onRequestEntities();
}
render() {
return (
<EntityComponent {...this.props} />
)
}
}
return connect(mapStateToProps, mapDispatchToProps)(EntityComponent);
}
and the volunteer should look like:
const volunteerHoc = WithEntity (volunteer, action, columns, name);
const consumerHoc = WithEntity (consumer, action, columns, name)
but I did not understand how to inject the Loading and Table components, and wht the name of the class inside the HOC should be-
should I use another HOC - something like WithLoader that receive the data from the first one and render the Loading and Table components with the proper data? just to mention that connect is HOC itself so I need to return the EntityComponent to the redux store :
return connect(mapStateToProps, mapDispatchToProps)(EntityComponent);
I Would appreciate any help
OK, I made it, the HOC takes a basic component, Expands the functionality (by adding methods and managing state for ex) and return a new (henanced) comp with this props.
lets create a simple volunteer comp:
import React, {Component} from 'react';
import { requestVolunteerData } from '../actions/entitiesAction';
import { volenteerColumns as columns } from '../utils/entitiesColumns/volenteerColumns';
import '../container/App.css';
import WithEntity from '../components/HOC/WithEntity.jsx';
import Table from '../components/Table/Table';
import Loading from '../components/Loading/Loading';
class Volenteer extends Component {
render() {
const { entities, isPending} = this.props;
return isPending ?
<Loading />
:
(
<div className='tc'>
<h1 className='f2'>רשימת מתנדבים</h1>
<Table data={ entities } columns={ columns } />
</div>
);
}
}
const VolenteerHOC = WithEntity(Volenteer, requestVolunteerData() );
export default VolenteerHOC;
now lets create the HOC WithEntity that managing the state and return the new cmop to redux state by connect:
import React, {Component} from 'react';
import { connect } from 'react-redux';
const WithEntity = (EntityComponent, action) => {
const mapStateToProps = state => {
return {
isPending: state.requestEntitiesReducer.isPending,
entities: state.requestEntitiesReducer.entities,
error: state.requestEntitiesReducer.error
}
}
const mapDispatchToProps = dispatch => {
return {
onRequestEntities: () => dispatch(action)
}
}
class NewCmoponent extends Component {
componentDidMount () {
this.props.onRequestEntities();
}
render() {
const { entities, isPending} = this.props;
return (
<EntityComponent {...this.props} />
)
}
}
return connect(mapStateToProps, mapDispatchToProps)(NewCmoponent );
}
export default WithEntity;
Now same route can be simply generated via this HOC.
check out this video:
https://www.youtube.com/watch?v=rsBQj6X7UK8

React pass child class method to parent functional component

I am trying to get adaptValue from Component1 and use it in Component2. For some reason this does not work since my adaptValue is always null/undefined. Is it because Parent is a functional component?
const Parent = (props) => {
const [adaptValue, setAdapt] = useState(null);
return (
<div>
<Component1 setAdapt={setAdapt}/>
<Component2 adaptValue={adaptValue}/>
</div>
)
}
export default class Component1 extends Component {
constructor(props) {
super(props);
}
adaptValue = (value) =>{
DO_SOMETHING_WITH_VALUE
}
componentDidMount() {
this.props.setAdapt(this.adaptValue);
}
render() {
return something;
}
}
export default class Component2 extends Component {
constructor(props) {
super(props);
}
someFunction = (value) =>{
...
//adaptValue is always undefined
this.props.adaptValue(value)
...
}
render() {
return something;
}
}
UPDATE Made the parent a class component in the end and all works. Wondering whether this is a compatibility issue between functional or class-based components.
When passing setAdapt to Component1 ... setAdapt is already a function. There is no need to wrap it in another one. Component1 will modify the value, and Component2 will display it. Function Components have nothing to do with the behavior.
Try ...
App.js
import React, { useState } from "react";
import "./styles.css";
import Component1 from "./Component1";
import Component2 from "./Component2";
export default function App() {
const [adaptValue, setAdapt] = useState(null);
return (
<div>
<Component1 setAdapt={setAdapt} />
<Component2 adaptValue={adaptValue} />
</div>
);
}
Component1.js
import React, { Component } from "react";
export default class Component1 extends Component {
handleClick = () => {
this.props.setAdapt("New Value");
};
render() {
return <button onClick={() => this.handleClick()}>Set Value</button>;
}
}
Component2.js
import React, { Component } from "react";
export default class Component2 extends Component {
render() {
return !!this.props.adaptValue ? (
<h1>{`"${this.props.adaptValue}" <- Value of adaptValue`}</h1>
) : (
<h1>adaptValue Not Assigned</h1>
);
}
}
Sandbox Example ...

New Components in Application cannot connect to redux

I have created a small application and connected it to Redux. Unfortunately when creating new components and using the same exact code those new components cannot seem to connect to redux and get undefined when accessing it (using mapStateToProps).
I have tried to create new Components and connect them again to no avail. I'm kind of at loss as to why it isn't working especially since the rest of the application can connect and get the state properly
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { Provider } from 'react-redux'
import store from './store'
ReactDOM.render(
<Provider store={store} >
<App />
</Provider>
, document.getElementById('root'));
store.js:
const initialState = {
guessedTimezone: '',
timezone: '',
pseudo: '',
};
function rootReducer(state = initialState, action) {
console.log(action);
if (action.type === 'CHANGE_TIMEZONE') {
return Object.assign({}, state, {
timezone: action.timezone,
guessedTimezone: action.guessedTimezone
})
}
if (action.type === 'CHANGE_PSEUDO') {
return Object.assign({}, state, {
pseudo: action.pseudo,
token: action.token
})
}
return state;
}
export default rootReducer;
new Component not connecting:
import React, { Component } from 'react'
import { connect } from 'react-redux'
export class TestPseudo extends Component {
render() {
console.log(this.props.pseudo);
return (
<div>
{this.props.pseudo}
</div>
)
}
}
const mapStateToProps = state => {
return {
pseudo: state.pseudo
}
}
export default connect(mapStateToProps)(TestPseudo)
Here for example this.props.pseudo returns undefined when, if the connection happens, it should return the value if i understand it correctly and yet it shows undefined
EDIT:
App.js as per requested :
import React, { Component } from 'react'
import { connect } from 'react-redux'
import Homepage from './Components/Homepage';
import moment from 'moment';
import moment_timezone from 'moment-timezone';
import HeaderApp from './Components/HeaderApp';
import { TestPseudo } from './Components/TestPseudo';
export class App extends Component {
async componentDidMount() {
let tz = moment.tz.guess(true);
let date = moment(new Date()).local();
let timezone = date['_i'].toString().split('(')[1].split(')')[0];
this.props.dispatch({
type: 'CHANGE_TIMEZONE',
guessedTimezone: tz,
timezone: timezone
})
console.log(`Guessed timezone: ${tz} (${timezone})`);
}
_showHomepage() {
if (this.props.showHomepage && this.props.loaded) {
return (
<div style={styles.mainWindow}>
{/*<Homepage click={this._handleClick} />*/}
<TestPseudo />
</div>
)
}
}
_showHeader() {
return (
<div>
<HeaderApp />
</div>
)
}
render() {
return (
<div>
{this._showHeader()}
{this._showHomepage()}
</div>
)
}
}
const styles = {
mainWindow: {
height: '100vh',
width: '100vw'
}
}
const mapStateToProps = state => {
return {
guessedTimezone: state.guessedTimezone,
timezone: state.timezone,
};
};
export default connect(mapStateToProps)(App);
I call that new Component instead of my old Component. The homepage can connect but not the new one so i think it's not a problem of emplacement
I think its here
import { TestPseudo } from './Components/TestPseudo';
You are importing the non-connected component. Try this
import TestPseudo from './Components/TestPseudo';
For your understanding, exporting as default can be imported like so;
export default Component
import WhateverName from ....
Named export like const or in your case class;
export class Component
import { Component } from ...
So use brackets when Named, and skip brackets when default.

Can i pass component state to HoC?

Is there any way to send data from the component's state to HoC?
My component
import React, { Component } from 'react';
import withHandleError from './withHandleError';
class SendScreen extends Component {
contructor() {
super();
this.state = {
error: true
}
}
render() {
return (
<div> Test </div>
)
}
};
export default withHandleError(SendScreen)
My HoC component:
import React, { Component } from 'react';
import { ErrorScreen } from '../../ErrorScreen';
import { View } from 'react-native';
export default Cmp => {
return class extends Component {
render() {
const { ...rest } = this.props;
console.log(this.state.error) //// Cannot read property 'error' of null
if (error) {
return <ErrorScreen />
}
return <Cmp { ...rest } />
}
}
}
Is there any way to do this?
Is the only option is to provide props that must come to the SendScreen component from outside??
A parent isn't aware of child's state. While it can get an instance of a child with a ref and access state, it can't watch on state updates, the necessity to do this indicates design problem.
This is the case for lifting up the state. A parent needs to be notified that there was an error:
export default Cmp => {
return class extends Component {
this.state = {
error: false
}
onError() = () => this.setState({ error: true });
render() {
if (error) {
return <ErrorScreen />
}
return <Cmp onError={this.onError} { ...this.props } />
}
}
}
export default withHandleError(data)(SendScreen)
In data you can send the value you want to pass to HOC, and can access as prop.
I know I answer late, but my answer can help other people
It is very easy to do.
WrappedComponent
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import HocComponent from './HocComponent';
const propTypes = {
passToHOC: PropTypes.func,
};
class WrappedComponent extends Component {
constructor(props) {
super(props);
this.state = {
error: true,
};
}
componentDidMount() {
const {passToHOC} = this.props;
const {error} = this.state;
passToHOC(error); // <--- pass the <<error>> to the HOC component
}
render() {
return <div> Test </div>;
}
}
WrappedComponent.propTypes = propTypes;
export default HocComponent(WrappedComponent);
HOC Component
import React, {Component} from 'react';
export default WrappedComponent => {
return class extends Component {
constructor() {
super();
this.state = {
error: false,
};
}
doAnything = error => {
console.log(error); //<-- <<error === true>> from child component
this.setState({error});
};
render() {
const {error} = this.state;
if (error) {
return <div> ***error*** passed successfully</div>;
}
return <WrappedComponent {...this.props} passToHOC={this.doAnything} />;
}
};
};
React docs: https://reactjs.org/docs/lifting-state-up.html
import React, { Component } from 'react';
import withHandleError from './withHandleError';
class SendScreen extends Component {
contructor() {
super();
this.state = {
error: true
}
}
render() {
return (
<div state={...this.state}> Test </div>
)
}
};
export default withHandleError(SendScreen)
You can pass the state as a prop in your component.

Trouble using actions in react-redux presentational component

I'm new to redux and having trouble wrapping my head around presentational and container components.
Relevant stack:
react v0.14.8
react-native v0.24.1
redux v3.5.2
react-redux v4.4.5
The issue:
I have a login button component, which when rendered checks the login status and calls the onSuccessfulLogin action which updates the state with the user's Facebook credentials.
However, when trying to separate this into separate presentational/container components, I'm unable to call the onSuccessfulLogin action: Error: onSuccessfulLogin is not defined.
What am I doing wrong here? I'd imagine there's something simple that I'm not understanding with the relationship between the two components and the connect() function.
Presentational Component (Login.js)
import React, { PropTypes } from "react-native";
import FBLogin from "react-native-facebook-login";
import UserActions from "../users/UserActions";
class LoginPage extends React.Component {
render() {
const { userData, onSuccessfulLogin } = this.props;
return (
<FBLogin
permissions={["email","user_friends"]}
onLoginFound= { data => {
onSuccessfulLogin(data.credentials);
}}
/>
)
}
};
export default LoginPage;
Container Component (LoginContainer.js)
import { connect } from 'react-redux';
import LoginPage from "../login/LoginPage";
import UserActions from "../users/UserActions";
const mapDispatchToProps = (dispatch) => {
return {
onSuccessfulLogin: (userData) => {
dispatch(UserActions.userLoggedIn(userData))
}
}
}
const mapStateToProps = (state) => {
return {
userData: state.userData
}
}
const LoginContainer = connect(
mapStateToProps,
mapDispatchToProps
)(LoginPage);
export default LoginContainer;
Also, if I wanted to make the updated state.userData accessible to the LoginPage component, how would I do that? Any help is appreciated!
Solved! When using ES6 classes, you're required to call super(props) in a constructor method in order to access the container's properties in the connected presentational component:
class LoginPage extends React.Component {
constructor(props){
super(props);
}
render(){
// ...
}
}
Your container component is supposed to be a component and it must have a render function with the dumb/presentational components you want to render.
import { connect } from 'react-redux';
import LoginPage from "../login/LoginPage";
import UserActions from "../users/UserActions";
class LoginContainer extends React.Component {
constructor(props){
super(props);
}
render() {
return (
<LoginPage userData={this.props.userData}
onSuccessfulLogin={this.props.onSuccessfulLogin}
/>
)
}
};
const mapDispatchToProps = (dispatch) => {
return {
onSuccessfulLogin: (userData) => {
dispatch(UserActions.userLoggedIn(userData))
}
}
}
const mapStateToProps = (state) => {
return {
userData: state.userData
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(LoginPage);

Categories

Resources