Pass dynamic value to HOC in react - reactjs

I write some HOC and I need to pass to this HOC a dynamic object that I create on some life cycle level and I did not get him as a prop.
If I try to pass some static value ( for example initialize myObj from start) it works as expected and I get the correct value.
Let's say this is my component class :
let myObj = {};
class Test extends React.Component
{
constructor(props) {
super(props);
.....
}
render() {
myObj = {test:'test'};
return ( ... )
}
}
export default withHOC(Test, myObj);
And this is my HOC:
const withHOC = (Component, test) => {
class Hoc extends React.Component
{
constructor(props)
{
super(props);
const s = test; // ---->test is empty object always !!
...
}
}
return Hoc;
}
My 'Dynamic' object that I create on my 'test' class is always empty on my HOC class.
It's happend also when I try to pass some value from my props directly, in this case the page is stuck(without errors in console).
Does someone have any idea how to resolve that? Thanks!

When you compose a component that way, composition only happens at compile time (static composition). This means that withHOC runs only once and is receiving an empty myObj argument, as it is using the one defined on declaration.
export default withHOC(Test, myObj); //myObj = {}
If you want that value to be dynamic, the withHOC composition should be runned when that value changes.
You can't send data up from the WrappedComponent (Test) to the HOC (withHOC), so even if you change myObj value in Test.render, the HOC would never know.
What you could do, if you really need it, is do the composition on the Test.render
render(){
const Hoc = withHOC(this.state.myObj, WrappedComponent);//WrappedComponent can be anything
return(
<Hoc/>
)
}
This way, every time the component renders, Hoc is composed using as myObj a value from the component state, wich is not the preferable way to do it, because this.state.myObj might have the same value as it did at the previous render, and you would be re-composing with the same data as before.
A better way to do it is checking for changes in myObj at Test.componentDidUpdate, and if it did change, then compose Hoc again.

You are passing an empty object to the withHOC function
let myObj = {}; // <- your myObj is empty
class Test extends React.Component
{
constructor(props) {
super(props);
.....
}
render() {
myObj = {test:'test'}; // <- You're doing this in the render method of your Test component, so until the component is rendered myObj is empty
return ( ... )
}
}
export default withHOC(Test, myObj);

Some explanation about what's happening here, by order:
import Comp from '.../Test.js'
the withHOC function is triggered, with the params of Test (which is defined above the call) and myObj (which is defined above the call but is empty)
Test component is returned, and nobody used the logic of myObj = {test:'test'}
Suggested solution:
Make the HOC get the logic from the props with another hoc:
const withProps = newProps => BaseComponent => props => {
const propsToAdd = typeof newProps === 'function' ? newProps(props) : newProps
return <BaseComponent {...props} {...propsToAdd} />
}
Usage:
class Test extends React.Component
{
constructor(props) {
super(props);
.....
}
render() {
return ( ... )
}
}
export default withProps({test:'test'})(withHOC(Test));
// or: export default withProps(props => {test:'test'})(withHOC(Test));
const withHOC = (Component) => {
class Hoc extends React.Component
{
constructor(props)
{
super(props);
const s = this.props.test;
...
}
}
return Hoc;
}
you can use recompose, a library which has many hocs and utils, and for better readability:
import { compose, withProps } from "recompose"
class Test extends React.Component {...}
const enhance = compose(
withProps({test:'test'}),
withHOC
)
export default enhance(Test);

I can't say with confidence this is optimal but I solved a similar problem by having a function within the HOC that updates state that you can then invoke with any data in the wrapped component.
HOC:
func = (a, b) => {
this.setState({
stateA: a,
stateB: b
)}
}
return ({ <WrappedComponent func={this.func} /> })
Wrapped Component:
this.props.func(anythingA, anythingB);
You can then access the data through state in the HOC.
To elaborate:
const withHOC = (WrappedComponent) => {
class withHOC extends React.Component {
constructor(props) {
super(props)
this.state = {
stateA: 1,
stateB: 2
}
*use state however you want in this HOC, including pass it through to another component*
*the following is just a function*
*when it's invoked in the wrapped component state will update here in the
HOC*
changeState = (a, b) => {
this.setState({
stateA: a,
stateB: b
)}
}
render() {
return (
<div>
<p>this.state.stateA</p>
<p>this.state.stateB</p>
<WrappedComponent changeState={this.changeState} />
</div>
)
}
}
}
}
In wrappedComponent, after importing:
class aComponent extends Component {
constructor(props) {
super(props)
this.state = {
}
*you can now invoke the function from the HOC in this wrapped component*
}
}

You can use react-redux and store your object in redux state. Change the object wherever you need (in your case it's in Test) and access it in component inside your HOC from redux state, it'll be always up to date.

Related

HoC component connected to redux arround component connected to redux

I have base component, let's say BaseContainer that connects to redux and has some methods. Now I want to create few CustomContainer components that should be connected to redux too and should have access to all methods and state of BaseContainer component.
So BaseContainer would be:
class BaseContainer extends React.Component {
state = {};
method1() {};
method2() {};
method3() {};
}
export default connect(mapStateToProps, mapDispatchToProps)(BaseContainer);
And one of CustomContainers should be:
class CustomContainer extends BaseContainer {
// should have access to all imports, methods and props of BaseContainer
}
export default connect(mapStateToProps, mapDispatchToProps)(CustomContainer);
Tried this but seems that inheritance does not work well in React and it is not recommended too.
Here I get error Super expression must either be null or a function.
Tried other approach with using HoC:
class CustomContainer extends React.Component {
// should have access to all imports, methods and props of BaseContainer
}
export default connect(mapStateToProps, mapDispatchToProps)(BaseContainer(CustomContainer));
and now I'm facing error: Unhandled Rejection (TypeError): Object(...) is not a function
What is wrong and how can I achieve that my CustomContainer has access to all imports, props and state of BaseContainer ?
You should probably read over the react docs, specifically Composition vs. Inheritance. React favors composition over inheritance. BaseContainer also isn't a Higher Order Component, but rather it's a regular component, and it doesn't appear to return anything to render.
Higher Order Component
Here's an implementation I think would help get you close to what you're after
const withBaseCode = WrappedComponent => {
class BaseContainer extends Component {
state = {};
method1 = () => {...}
method2 = () => {...}
method3 = () => {...}
render() {
return (
<WrappedComponent
method1={this.method1}
method2={this.method2}
method3={this.method3}
{...this.props}
/>
);
}
}
const mapStateToProps = state => ({...});
const mapDispatchToProps = {...};
return connect(mapStateToProps, mapDispatchToProps)(BaseContainer);
};
Then to use it's just a normal HOC, so given some component
const CustomContainer = ({ method1, method2, method3, ...props}) => {
...
return (
...
);
};
const CustomContainerWithBaseCode = withBaseCode(CustomContainer);
Some App container
function App() {
return (
<div className="App">
...
<CustomContainerWithBaseCode />
</div>
);
}
Demo of above code minus actually connecting to a redux store.

How to get the data from React Context Consumer outside the render

I am using the new React Context API and I need to get the Consumer data from the Context.Consumer variable and not using it inside the render method. Is there anyway that I can achieve this?
For examplify what I want:
console.log(Context.Consumer.value);
What I tested so far: the above example, tested Context.Consumer currentValue and other variables that Context Consumer has, tried to execute Context.Consumer() as a function and none worked.
Any ideas?
Update
As of React v16.6.0, you can use the context API like:
class App extends React.Component {
componentDidMount() {
console.log(this.context);
}
render() {
// render part here
// use context with this.context
}
}
App.contextType = CustomContext
However, the component can only access a single context. In order to use multiple context values, use the render prop pattern. More about Class.contextType.
If you are using the experimental public class fields syntax, you can use a static class field to initialize your contextType:
class MyClass extends React.Component {
static contextType = MyContext;
render() {
let value = this.context;
/* render something based on the value */
}
}
Render Prop Pattern
When what I understand from the question, to use context inside your component but outside of the render, create a HOC to wrap the component:
const WithContext = (Component) => {
return (props) => (
<CustomContext.Consumer>
{value => <Component {...props} value={value} />}
</CustomContext.Consumer>
)
}
and then use it:
class App extends React.Component {
componentDidMount() {
console.log(this.props.value);
}
render() {
// render part here
}
}
export default WithContext(App);
You can achieve this in functional components by with useContext Hook.
You just need to import the Context from the file you initialised it in. In this case, DBContext.
const contextValue = useContext(DBContext);
You can via an unsupported getter:
YourContext._currentValue
Note that it only works during render, not in an async function or other lifecycle events.
This is how it can be achieved.
class BasElement extends React.Component {
componentDidMount() {
console.log(this.props.context);
}
render() {
return null;
}
}
const Element = () => (
<Context.Consumer>
{context =>
<BaseMapElement context={context} />
}
</Context.Consumer>
)
For the #wertzguy solution to work, you need to be sure that your store is defined like this:
// store.js
import React from 'react';
let user = {};
const UserContext = React.createContext({
user,
setUser: () => null
});
export { UserContext };
Then you can do
import { UserContext } from 'store';
console.log(UserContext._currentValue.user);

React Recompose call a bound method of an enhaced component

Using recompose is it possible to call a bound method of an enhaced component? For instance the onClick on the example below on "SomeOtherComponent"
class BaseComponent extends Component {
constructor (props) {
super(props)
this.myBoundMethod = this._myBoundMethod.bind(this)
}
_myBoundMethod () {
return this.something
}
render () {
return (<h1>{'Example'}</h1>)
}
}
const Enhaced = compose(
/* Any number of HOCs ...
lifecycle,
withProps,
withStateHandlers
*/
)(BaseComponent)
class SomeOtherComponent extends Component {
constructor (props) {
super(props)
this.handleClick = this._handleClick.bind(this)
}
_handleClick () {
console.log(this._enhacedComponent.myBoundMethod())
}
render () {
<div>
<Enhaced ref={(c) => {this._enhacedComponent = c}} />
<button onClick={this.handleClick}>Click</Button>
</div>
}
}
I'm aware of hoistStatics but I don't know how to make it for a bound method.
hoistStatics only hoists static properties, but what you need is instance methods.
Here is a way to achieve what you want in Recompose. First, rename ref callback to, for example, forwardedRef:
<Enhaced fowardedRef={(c) => { this._enhacedComponent = c }} />
Then, use withProps as the last HOC to rename fowardedRef to ref:
const Enhaced = compose(
/* ... other HOCs ... */
withProps(({ fowardedRef }) => ({ ref: fowardedRef }))
)(BaseComponent)
Now, the ref callback is passed to BaseComponent.
The whole running example is here https://codesandbox.io/s/6y0513xpxk

How to declare ReactJS default props when using Redux?

What is the right way to declare default props in react so that when I call map on a prop that is asynchronously assigned using redux I do not get an undefined error? Right now, with the following syntax I get an error when trying to assign trans_filter because data is undefined in the initial call to render.
class ContainerComponent extends React.Component {
static defaultProps = {
searchProps: {
data: []
}
};
constructor(props) {
super(props);
}
render(){
let trans_filter = JSON.parse(JSON.stringify(this.props.searchProps.data));
}
}
const mapStateToProps = (state) => ({
searchProps: state.searchProps
});
export default connect(mapStateToProps, {getTransactionsAll})(ContainerComponent);
Here's how you can declare default props when using the ES6 class syntax for creating ReactJS components:
class ContainerComponent extends React.Component {
constructor(props) {
super(props);
}
render(){
let trans_filter = JSON.parse(JSON.stringify(this.props.searchProps.data));
}
}
ContainerComponent.defaultProps = {
searchProps: {
data: []
}
};
export default ContainerComponent;
Additionally, there is another syntax for declaring defaultProps. This is a shortcut, but it will work only if your build has ES7 property initializers turned on. I assume that's why it doesn't work for you, because I see no issues with your syntax:
class ContainerComponent extends React.Component {
static defaultProps = {
searchProps: {
data: []
}
};
constructor(props) {
super(props);
}
render() {
let trans_filter = JSON.parse(JSON.stringify(this.props.searchProps.data));
}
}
export default ContainerComponent;
Edit: after you shared your mapStateToProps, yes, it has something to do with Redux!
The issue is caused by your reducer. You must declare initial state shape and moreover, you must specify the initial state in each reducer. Redux will call our reducer with an undefined state for the first time. This is our chance to return the initial state of our app.
Set initial state:
const searchPropsInitialState = {
data: []
};
Then, in your reducer when you manipulate searchProps do:
function yourReducer(state = searchPropsInitialState, action) {
// ... switch or whatever
return state;
}
For more details, see handling actions in the Redux docs.

Higher order component always rerenders ignoring shouldComponentUpdate

I have a higher order component like this
// higherOrderComponent.js
const HigherOrderComponent = Component => class extends React.Component {
shouldComponentUpdate (nextProps, nextState) {
return false
}
render () {
return <Component {...this.props} />
}
}
export default HigherOrderComponent
// myComponent.js
import HigherOrderComponent from './higherOrderComponent'
class MyComponent extends React.Component {
render () {
return <div>my component</div>
}
}
export default HigherOrderComponent(MyComponent)
// parentComponent.js
import MyComponent from './myComponent'
class ParentComponent extends React.Component {
render () {
return <MyComponent />
}
}
I explicitly return false but the component always get re-rendered. Any idea why? I ultimately want to share the "shouldComponentUpdate" across components. How can I achieve that if higher order component does not work?
since you have not specified how you are invoking your Higher Order component, based on the issue I have made a guess how you might be using it.
My Answer is based on the assumption that you are invoking your higher order function like
var MyHigherOrderFn = (HigherOrderComponent(Baar));
If Some you how you can invoke your higher order function like below into return in render, you can avoid the issue.
<HigherOrderComponent prop1="Hello" child="Child" />
Since I don;t know how invoke your function in above way(I am not sure its even possible), I have created HigherOrderComponent2 with different syntax style which can be invoked like, which in turn comply with shouldComponentUpdate
<Parent prop1="val1">
<Child>
</Parent>
import React, {PropTypes} from 'react';
/*This is simeple child component*/
class Baar extends React.Component {
render() {
return (<div>{this.props.name}</div>);
}
}
/*This is your higher order component*/
const HigherOrderComponent = Component => class extends React.Component {
shouldComponentUpdate (nextProps, nextState) {
return false;
}
render () {
return <Component {...this.props} />
}
}
/*This is another way to write higher order component*/
class HigherOrderComponent2 extends React.Component {
constructor(props) {
super(props);
}
shouldComponentUpdate (nextProps, nextState) {
return false;
}
render(){
let child = this.props.children && React.cloneElement(this.props.children,
{...this.props}
);
return <div>{child}</div>
}
}
/*Problem that you are facing how you invoke your Higher Order Compoent*/
export default class Foo extends React.Component {
constructor(props) {
super(props);
this.onHandleClick = this.onHandleClick.bind(this);
this.state={
name: 'Praveen Prasad'
}
}
onHandleClick(){
this.setState({
name:Math.random()
});
}
render() {
{'This is how you might be invoking you higher order component, at this time react render doesnt know it already exists in DOM or not'}
{'this component will always re-render, irrespective of values in shouldComponentUpdate'}
var Baaz = (HigherOrderComponent(Baar));
return (<div>
<button onClick={this.onHandleClick}>Update Name</button>
<Baaz name={this.state.name} />
{'This is another way to invoke higher order Component , and this will respect shouldComponentUpdate'}
<HigherOrderComponent2 name={this.state.name}>
<Baar />
</HigherOrderComponent2>
</div>);
}
}
I have modified your code to create a snippet and it works as intended, MyComponent.render is called only once when shouldComponentUpdate returns false.
My guess is that somehow you are using the unwrapped version of MyComponent instead of the wrapped one. Maybe a problem with your build environment?
// higherOrderComponent.js
const HigherOrderComponent = Component => class extends React.Component {
shouldComponentUpdate (nextProps, nextState) {
return false;
}
render () {
return <Component {...this.props} />
}
}
class MyComponent extends React.Component {
render () {
console.log('render');
return <div>my component</div>
}
}
const MyComponentHOC = HigherOrderComponent(MyComponent);
class ParentComponent extends React.Component {
render () {
return <MyComponentHOC />
}
}
ReactDOM.render(<ParentComponent/>, document.getElementById('container'));
ReactDOM.render(<ParentComponent/>, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>
It is about the life cycle for a react component. When a component get initialized, it will not check shouldComponentUpdate. ShouldComponentUpdate will only be called when state/props CHANGED.
FYI the lifecycle methods call in order:
When a component is initialized:
getDefaultProps
getInitialStage
componentWillMount
render
componentDidMount
When a component has state changed:
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
When a component has props changed:
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
When a component is unmounting:
componentWillUnmount
You would need to use a different type of HOC pattern called inheritance inversion to access the lifecycle methods. Since you are overriding shouldComponentUpdate you don't call super however it is required to call super.render() inside the subclassed components render method.
Try this...
const HigherOrderComponent = () => WrappedComponent =>
class ShouldNotUpdate extends WrappedComponent {
shouldComponentUpdate(nextProps) {
return false
}
render() {
return super.render()
}
}
it's good practice to use currying so as you could annotate your classes in the future like...
#HigherOrderComponent
class MyClass extends React.Component {
render() {
return <div>something</div>
}
}
// or without annotations..
const MyNewClass = HigherOrderComponent()(MyClass)

Resources