How to pass arguments to a function inside compose? - javascript

I have a function like so:
export const fireView = (prop1, prop2) => WrappedComponent =>
class extends Component {
//stuff in here
}
then a hoc like this:
export const withFireView = (prop1, prop2) =>
fireView(prop1, prop2)
but now I want to put this function inside compose as I need to call it with mapStateToProps
so I did this:
compose (
connect(mapStateToProps),
fireView
)
but it breaks because You must pass a component to the function returned by connect
so I then I get rid of the arguments in the fireview function and I think this will work but then all the arguments are now undefined inside the function as I've not passed them
Is there any way to do something like this:
compose (
connect(mapStateToProps),
fireView(arg1, arg2)
)
but obviously, they are not defined there if that makes sense.

Here is a full working example:
var Component = React.Component;
var render = ReactDOM.render;
var Provider = ReactRedux.Provider;
var connect = ReactRedux.connect;
var createStore = Redux.createStore;
var compose = Redux.compose;
const reducer = () => {return {}};
const mapStateToProps = () => {
return {};
};
const wrapped = (props) => {
return <div>{props.prop1} {props.prop2}</div>;
}
const fireView = (prop1, prop2) => WrappedComponent => {
return class extends Component {
render() {
return <WrappedComponent prop1={prop1} prop2={prop2} />;
}
}
}
const withFireView = (prop1, prop2) => fireView(prop1, prop2);
const withFireViewAndConnect = (arg1, arg2) => compose(connect(mapStateToProps), withFireView(arg1, arg2));
const App = withFireViewAndConnect('some', 'arg')(wrapped);
render(<Provider store={createStore(reducer)}><App /></Provider>, document.getElementById('demo'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.13.0/polyfill.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.1/redux.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/6.0.0/react-redux.js"></script>
<div id="demo"></div>

I had a similar requirement and ended up on this page. I supposed we were not able to understand the use case. I will try to summarise it and also provide an answer to it.
Explanation
We can use HOC (Higher Order Components) in React to abstract out common functionality. In React docs, we can see the 'with' notation to use these HOCs. I have created a few on my app as well.
withSplashScreenRedirect, withHistoryObject
All these HOCs that I created takes a Component (WrappedComponent) and returns a Component. (No additional Arguments required).
const EnhancedComponent = higherOrderComponent(WrappedComponent);
I would generally have around 2 to 3 HOCs on each Screen level Component. Writing these HOCs as functions returning other functions look ugly. Redux provides a useful compose function. Compose helps us to chain these HOCs in a more readable manner.
From compose docs.
All compose does is let you write deeply nested function transformations without the rightward drift of the code. Don't give it too much credit!
Soon I wanted to create another HOC that would standardise my API call logic and API call handling logic. In this case, I had to send two additional functions to my HOC (along with the Wrapped Component).
withApiCallHandler(WrappedComponent, makeApiCall, handleApiResponse)
Passing props to HOC is not a big deal. React Docs have a good example explaining how to do it. However, adding such an HOC to compose chain is not so straightforward. I guess that is what OP was trying to ask here.
Answer
I wasn't able to find a way to chain such a HOC with compose. Maybe it's not possible. The best solution would be to initialise the withApiCallHandler Component separately and then chain it with compose.
Instead of
const ComposedScreenComponent = compose(
withHOC1,
withHOC2,
withHOC3(arg1, arg2)
)(ScreenComponent);
Do this
const withHOC3AndArgs = (WrappedComponent) =>
withHOC3(WrappedComponent, arg1, arg2);
const ComposedScreenComponent = compose(
withHOC1,
withHOC2,
withHOC3AndArgs(arg1, arg2)
)(ScreenComponent);
This is not the best answer to the question, but if someone is stuck then this solution will surely be a good workaround.

You can make withFireView return a function instead. All will work.
export const withFireView = (prop1, prop2) => () => fireView(prop1, prop2)

Related

React HOC: Pass data attributes to the first child/element of wrapped component

I have a hoc component like this:
export const withAttrs = (WrappedComponent) => {
const ModifiedComponent = (props) => (
<WrappedComponent {...props} data-test-id="this-is-a-element" />
);
return ModifiedComponent;
};
export default withAttrs;
and I use it like this:
import React from 'react';
import withAttrs from './withAttrs';
const SomeLink = () => <a><p>hey</p</a>;
export default withAttrs(SomeLink);
I expect to have an anchor tag like this:
<a data-test-id="this-is-a-element"><p>hey</p></a>
But the hoc doesn't add the data-attribute to the first element. Is there a way to achieve this?
But the hoc doesn't add the data-attribute to the first element.
It's not the HOC that isn't adding it, it's SomeLink, which doesn't do anything with the props the HOC passes to it.
The simple answer is to update SomeLink:
const SomeLink = (props) => <a {...props}><p>hey</p></a>;
That's by far the better thing to do than the following.
If you can't do that, you could make your HOC add the property after the fact, but it seems inappropriate to have the HOC reach inside the component and change things. In fact, React makes the element objects it creates immutable, which strongly suggests you shouldn't try to mess with them.
Still, it's possible, it's probably just a bad idea:
export const withAttrs = (WrappedComponent) => {
const ModifiedComponent = (props) => {
// Note we're *calling* the function, not just putting it in
// a React element via JSX; we're using it as a subroutine of
// this component rather than as its own component.
// This will only work with function components. (You could
// write a version that handles class components as well,
// but offhand I don't think you can make one HOC that handles
// both in this case.)
const result = WrappedComponent(props);
return {
...result,
props: {
...result.props,
"data-test-id": "this-is-a-element",
},
};
};
return ModifiedComponent;
};
/*export*/ const withAttrs = (WrappedComponent) => {
const ModifiedComponent = (props) => {
// Note we're *calling* the function, not just putting it in
// a React element via JSX; we're using it as a subroutine of
// this component rather than as its own component.
// This will only work with function components. (You could
// write a version that handles class components as well,
// but offhand I don't think you can make one HOC that handles
// both in this case.)
const result = WrappedComponent(props);
// THIS IS PROBABLY A VERY BAD IDEA. React makes these objects
// immutable, probably for a reason. We shouldn't be mucking
// with them.
return {
...result,
props: {
...result.props,
"data-test-id": "this-is-a-element",
},
};
};
return ModifiedComponent;
};
const SomeLink = () => <a><p>hey</p></a>;
const SomeLinkWrapped = withAttrs(SomeLink);
const Example = () => {
return <div>
<div>Unwrapped:</div>
<SomeLink />
<div>Wrapped:</div>
<SomeLinkWrapped />
</div>;
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
/* So we can see that it was applied */
[data-test-id=this-is-a-element] {
color: green;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
Again, I don't think I'd do that except as a very last resort, and I wouldn't be surprised if it breaks in future versions of React.

React call functions on renderless component

I need to have a component for handling settings, this component (called Settings) stores state using useState(), for example the primary color.
I need to create a single instance of this component and make it available to every component in the app. Luckily, I already pass down a state dict to every component (I'm very unsure if this is the correct way to achieve that btw), so I can just include this Settings constant.
My problem is that I don't know how to create the component for this purpose, so that I can call its functions and pass it to children.
Here is roughly what my Settings component looks like:
const Settings = (props) => {
const [primaryColor, setPrimaryColor] = useState("")
const getColorTheme = (): string => {
return primaryColor
}
const setColorTheme = (color: string): void => {
setPrimaryColor(color)
}
return null
}
export default Settings
Then I would like to be able to do something like this somewhere else in the app:
const App = () => {
const settings = <Settings />
return (
<div style={{ color: settings.getColorTheme() }}></div>
)
}
Bear in mind that I'm completely new to react, so my approach is probably completely wrong.
You can use a custom Higher Order Component(HOC) for this purpose, which is easier than creating a context(even thougn context is also a HOC). A HOC takes a component and returns a new component. You can send any data from your HOC to the received component.
const withSettings = (Component) => {
const [settings, setSettings] = useState({})
// ...
// ...
<Component {...props} settings={settings}/>
);
And you can use it like this:
const Component = ({ settings }) => {
...your settings UI
}
export default SettingsUI = withSettings(Component);
You can read more about HOCs in the official react documentation

Unit testing connected higher-order components in React/Enzyme

In normal connected React components, in order to make unit testing easier, I've seen the pattern of exporting the base component, while export defaulting the connected component. It works well for simple tests that handle the actual functionality of the component without getting cluttered with redux stores and router mocking.
I'm looking for an analog for higher-order components. For instance, given a trivial higher-order component:
export default function exampleHoc(Wrapped) {
const ExampleHoc = props => (
<Wrapped {...props.reduxStore} hoc={true} />
);
return connect(
state => ({
reduxStore: state.example.reduxStore,
}))(ExampleHoc);
}
I'd like to verify that props.hoc === true in a unit test, without having to wire up the other higher-order components that <ExampleHoc> strings along.
My best design is passing along a flag:
export default function exampleHoc(Wrapped, exportUnconnectedForTest) {
const ExampleHoc = props => (
<Wrapped {...props.reduxStore} hoc={true} />
);
if (exportUnconnectedForTest) {
return ExampleHoc;
}
return connect(
state => ({
reduxStore: state.example.reduxStore,
}))(ExampleHoc);
}
This makes it easy to test
const DummyComponent = () => <div />;
const Sut = exampleHoc(DummyComponent, true);
...
test('props value should be passed', () => {
const sut = shallow(<Sut />);
const childProps = sut.find(DummyComponent).props();
expect(sut.childProps.hoc).to.equal(true);
});
while still happily passing along the wrapped output to any regular callers of the method.
It works, but it seems clunky to me. Is there a better/established pattern for delivering the unwrapped interior of a higher-order component?

React Stateful Components without classes

In React, we can write components as pure functions. However, the problem with this is that you can't use it as stateful components because of the lack of lifecycle hooks and state. So, I wonder if is there any way to create stateful components without using classes.
Something that I found is the createClass helper. But, React has moved this helper into their own package in the release 15.5.0, link. Also, they recommend that you migrate them to JavaScript classes because classes are now the preferred way to create components in React. Therefore, I don't think that using this helper could be a good idea.
On the other hand, Facebook recommends the use of High Order Components (HOCs) which is an advanced technique in React for reusing component logic. HOCs are not part of the React API, per se. They are a pattern that emerges from React's compositional nature. But, I couldn't find a way to create common stateful components without classes.
Has anyone gone through this? Is there any way to use React as a some purely functional solution?
React supports this since version 16.8. From the documentation:
Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.
A simple example:
import { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
For an example of how to use lifecycles, check out useEffect
Writing Stateful component without using classes is definitely a choice made by several developers. I recommend to use 'recompose' which has nice and easy implementation to write stateful components without class, yet apply state, both local and from store. Here is an example:
import compose from 'recompose/compose'
import withState from 'recompose/withState'
import withProps from 'recompose/withProps'
Pure.js
function MyComponent(props) ({
local: { prop1, prop2 },
setProp1
})
return <div>{prop1}</div>
}
const defaultState = {
prop1: false,
prop2: false
}
const enhance = compose(
withState('local', 'updateLocal', defaultState),
withProps(({ local: { prop1, prop2 }, updateLocal }) => ({
setProp1: (newValue) => updateLocal(state => ({...state, prop1: newValue }))
})))
export default enhance(MyComponent)
Maybe react-instance can become handy. Take a look at examples below.
Save state in local variable:
import React from "react"
import instance from "react-instance"
const App = instance(({ forceUpdate }) => {
let time = 0
const timer = setInterval(() => {
time++
forceUpdate()
}, 100)
return {
render() {
return time
},
unmount() {
clearInterval(timer)
},
}
})
Save state in component state:
import React from "react"
import instance from "react-instance"
const App = instance(instance => {
instance.state = { time: 0 }
const timer = setInterval(() => {
instance.setState({ time: instance.state.time + 1 })
}, 100)
return {
render() {
return instance.state.time
},
unmount() {
clearInterval(timer)
},
}
})
I tried to create a simple stateful component named Comp without the usage of es6 classes.
Here is the code
Basically I'm linking the prototype of the Comp function (our stateful component) to the prototype object of React.Component and I pass down to it Comp's props to initialize it properly. After that you can use every function of the React.Component object on the Comp.prototype. I used some just an example. I don't know if this is the best way in the "most javascript" way to use react

how to set displayName in a functional component [React]

I know that setting the displayName is sometimes required especially when you're dealing with production builds. I want to know how to set it using my functional component - is it possible/allowed?
Here's what I've got in my class component:
const MyComponent = React.createClass({
displayName: 'HeyHey',
render: function() {
console.log(this.displayName);
}
});
How do I do the same thing inside a stateless component?
The docs for displayName say
The displayName string is used in debugging messages. Usually, you don’t need to set it explicitly because it’s inferred from the name of the function or class that defines the component. You might want to set it explicitly if you want to display a different name for debugging purposes or when you create a higher-order component, see Wrap the Display Name for Easy Debugging for details.
In your case, you would simply use
const MyComponent = (props) => { ... }
MyComponent.displayName = 'HeyHey'
Or you can use Object.assign
const MyComponent =
Object.assign
( props => { ... }
, { displayName: 'HeyHey' }
)
Figured it out
const MyComponent = props => {
return (
<p>How you doin?</p>
)
}
MyComponent.displayName = "MyComponent"
React either needs displayName for functional components when they're defined as arrow functions, or the name of the function itself.
So for arrow functions:
const SomeComponent = () => <p>I come from an arrow function</p>
SomeComponent.displayName = 'HeyHey'
If you use a function, it'll use its name as displayName without having to define it separately:
function HeyHey() { return <p>I come from a non-arrow function!</p> }

Categories

Resources