HoC component connected to redux arround component connected to redux - reactjs

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.

Related

get props from children component through hoc

I have a component which is going through an hoc, but i want to get some props of this component inside the hoc. All works fine but i can not find out how to get the props out of this child component into the hoc.
here is the component which is going through the hoc, and that is this 'getAction' props i want to extract in the hoc
class ProjectPage2 extends Component {
render() {
return (
<Project2 getAction="getAction"/>
);
};
};
export default PageHandler(ProjectPage2)
here is the hoc component (imported as PageHandler in the ProjectPage2)
export default (ChildComponent) => {
class ComposedComponent extends Component {
render() {
// here i want to get the 'getAction' props, which is inside this ChildComponent
// because i need to use it into this hoc logic
return <ChildComponent {...this.props} />;
}
}
const mapStateToProps = (state) => {
return {
comments: state.project2
}
};
const loadData = (store) => {
return store.dispatch(getProject2());
};
return {
loadData,
component: connect(mapStateToProps, { getProject2 })(ComposedComponent)
}
};
if some one have an idea it would be great. Thanks.
I think you're very close already. It looks to me like you want the final result to be something like:
<Project2 getAction="getAction" comments={...} />
But what ends up getting rendered is just:
<Project2 getAction="getAction" />
See, the custom props of your HOC are passed to your child component via the child's props. You aren't using those, so the child just completely ignores the HOC. You can fix this with:
class ProjectPage2 extends Component {
render() {
return (
<Project2 getAction="getAction" {...this.props} />
);
};
}
Thank you for the answer. But it s not what i was looking for. I wanted to get into the HOC some children component's props passing through. But i finally get the idea which solved it. In fact it s so simple......
i wanted to pass the "getAction" string into the HOC. But i didn t find any solution to extract it (from the passing through component) there.
The solution is simply to pass it into the export default
class ProjectPage2 extends Component {
render() {
return (
// i was trying to use the component props
// <Project2 getAction="getAction"/>
// but no need
<Project2 />
);
};
};
// pass it into the fonction fix it
export default PageHandler(ProjectPage2, "getAction")
then get it in the HOC
export default (ChildComponent, varAction) => {
class ComposedComponent extends Component {
console.log(varAction) // return getAction

How to use currying to create HoC in React and connect to the Redux store at the same time?

I'm using React and Redux with react-redux, and I'm creating in React a High order Component that I want to connect to the Redux store, like this:
const HoC = parameter => WrappedComponent =>
return class WithSelected extends Component {
// ..some use of 'parameter'
render() {
return <WrappedComponent />
}
[...]
const exportComponent = compose(
connect(mapStateToProps),
HoC
)
export default exportComponent;
and
import Component from '../Component'
import exportComponent from '../file'
const componentToExport = exportComponent('someValue')(Component);
Now, this approach gives this error:
TypeError: Object(...) is not a function
Btw if I don't use currying creating the Hoc, it works, like this:
const HoC = (parameter, WrappedComponent) => [etc]
and
import Component from '../Component'
import exportComponent from '../file'
const componentToExport = exportComponent('someValue', Component);
It works. But how can I use currying creating HoC in React and use Redux at the same time?
There is nothing wrong with currying an HOC. The following is a valid high order component
const withHoc = param => Component => props =>{
return <Component {...props} foo={param} />
}
Your problem is compose. Your right-most argument which will provide the signature for the resulting composed function is actually evaluating to another HOC not the component itself.
You should connect the returned component
const HoC = parameter => WrappedComponent => connect(...)(props =>{
/*...*/
})
Returning a class based component is the same as returning a functional one
const HoC = parameter => WrappedComponent => connect(...)(class X extends Component{
render(){ return <div /> }
})
Just isn't that pretty though

Pass dynamic value to HOC in react

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.

React Native HOC's and duplicate lifecycle method functions

So I just discovered HOC's (Higher Order Functions) yesterday and they are pretty sweet. In my development I do use lifecycle methods like componentDidUpdate fairly frequently. I have found that I would like to use many HOCs for one wrapper component like so:
export default compose(
connect(mapStateToProps),
RefreshHOC(FeedScreen),
LoggedInHOC(FeedScreen)
)(FeedScreen)
I have noticed that if I have the same lifecycle (say componentDidUpdate) method in the WrapperComponent and one of the HOCs both lifecycle methods work. The problem arises when I have a Wrapper Component that has a lifecycle method then two or more HOC's also have the same lifecycle method, then only the first HOC's lifecycle method runs (in the above example componentDidUpdate runs in RefershHOC but not in LoggedInHOC).
Is there a better way to design this pattern? Am I just getting some syntax incorrect? Should I just have 1 HOC for each special lifecycle method that I want to group logic?
Edit
Here is some example code that I think is sufficient enough:
class FeedScreen extends Component {
componentDidUpdate(prevProps) {
let {appbase, auth, dispatch} = this.props
console.log('fire')
}
}
const mapStateToProps = (state) => ({
info: state.info,
auth: state.auth,
appbase: state.appbase
})
export default compose(
connect(mapStateToProps),
LoggedInHOC(FeedScreen),
LoggedInHOC2(FeedScreen)
)(FeedScreen)
export const LoggedInHOC = WrapperComponent => props => class
ViewWithPropChanges extends Component {
componentDidUpdate(prevProps) {
console.log('fire LIHOC')
}
render(){
return (<WrapperComponent {...this.props}/>)
}
}
}
export const LoggedInHOC2 = WrapperComponent => props => class ViewWithPropChanges extends Component {
componentDidUpdate(prevProps) {
console.log('fire LIHOC2')
}
render(){
return (<WrapperComponent {...this.props}/>)
}
}
EDIT
Some of your code seems a bit strange to me:
export const LoggedInHOC = WrapperComponent => props => class
// Later
export default compose(
LoggedInHOC(FeedScreen)
)(FeedScreen)
LoggedInHOC here is a function that takes a component and returns a function that returns a component when it should probably be only a function that takes a component and returns a component.
I'm going to assume that the role your LoggedInHOC is to check whether a user is connected somehow, display the wrapped component if that's the case and redirect the user/show a login form otherwise.
You could write it like that:
export const LoggedInHOC = Component => class extends React.Component {
render () {
// Check if the user is connected
if (connected) {
return (
<Component
{...this.props}
/>
);
}
return <p>User not connected</p>;
}
};
And you would wrap your component like that
export default LoggedInHOC(Component);
// Or if you want to chain multiple hocs:
export default compose(
LoggedInHOC,
AnotherHOC
)(Component);
Now back to your original question about chaining multiple HOCs and componentDidUpdate lifecycle. I'm not sure what is the exact problem in your case, but writting:
export default compose(
HOC1,
HOC2
)(Component);
is equivalent to HOC1(HOC2(Component)). So in term of composition you have:
HOC1
HOC2
Component
And you have to keep in mind that when your HOC1 wrapper is updated, that will trigger an update in your HOC2 and in your Component but if you update your HOC2, that will not trigger an update to your HOC1.
I made a example codepen that displays a component wrapped in multiple HOCs each implementing a componentDidUpdate hook

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

Resources