In my codebase I have a higher-order component (HOC) I use to add all the input validation functionality to a given component. It works great when used on a defined component like so...
let NameInput = React.createClass({
render() {
return (
<div>
<label htmlFor="name-input">Name</label>
<input name="name-input" />
</div>
);
}
});
let NameInput = addInputValidation(NameInput);
module.exports = NameInput;
But I now have a need to define a series of inputs based on an array from the server. Something like this...
let TestApp = React.createClass({
render() {
// Pretend the names array came from the server and is actually an array of objects.
let names = ['First name', 'Middle name', 'Last name'];
// Map over our names array in hopes of ending up with an array of input elements
let nameComponents = names.map((name, index) => {
let componentToRender = (
<div key={index}>
<label htmlFor={name}>{name}</label>
<input name={name} />
</div>
);
// Here is where I'd like to be able to use an HOC to wrap my name inputs with validation functions and stuff
componentToRender = addInputValidation(componentToRender);
return componentToRender;
})
return (
<div>
<p>Enter some names:</p>
{nameComponents}
</div>
);
}
})
let addInputValidation = function(Component) {
let hoc = React.createClass({
getInitialState() {
return {
isValid: false
};
},
render() {
return (
<div>
<Component {...this.props} />
{this.state.isValid ? null : <p style={{color: "red"}}>Error!!!!</p>}
</div>
);
}
});
return hoc;
}
module.exports = TestApp;
React doesn't like it when you try to render the result of calling an HOC from within another component.
I assume it has something to do with the fact that my componentToRender
isn't really a React component or something.
So my questions are...
Why can't I call a HOC from within another component?
Is there a way to call a HOC on each element of an array?
Here's a jsfiddle that might help: https://jsfiddle.net/zt50r0wu/
EDIT TO CLARIFY SOME THINGS:
The array that I'm mapping over is actually an array of objects that describe the details of the input. Including the type of input (select, checkbox, text, etc).
Also my addInputValidation
HOC actually takes more arguments than just the component. It takes an array of store indexes that will be pulled from the Redux store to be used for validation. These store indexes are derived from information in the array of objects describing the inputs. Having access to this potentially dynamic array is the reason I want to be able to call my HOC within the React life-cycle.
So mapping over my array of inputs might look more like this...
let Select = require('components/presentational-form/select');
let Text = require('components/presentational-form/select');
let CheckboxGroup = require('components/presentational-form/select');
let TestApp = React.createClass({
render() {
// Pretend the inputs array came from the server
let inputs = [{...}, {...}, {...}];
// Map over our inputs array in hopes of ending up with an array of input objects
let inputComponents = inputs.map((input, index) => {
let componentToRender = '';
if (input.type === 'select') {
componentToRender = <Select labelText={input.label} options={input.options} />;
} else if (input.type === 'text') {
componentToRender = <Text labelText={input.label} />;
} else if (input.type === 'checkbox') {
componentToRender = <CheckboxGroup labelText={input.label} options={input.options} />;
}
// Here is where I'd like to be able to use an HOC to wrap my name inputs with validation functions and stuff
componentToRender = addInputValidation(componentToRender, input.validationIndexes);
return componentToRender;
})
return (
<div>
<p>Enter some names:</p>
{inputComponents}
</div>
);
}
})
The thing I think you're tripping over is the distinction between a component vs. an element. I find it helpful to think of a component as a function and an element as the result of that function. So all you're really trying to do is conditionally choose one of three different functions, pass it some arguments, and the display the results. I believe you want something like this:
(this can be cleaned up, BTW, just tried to preserve your code structure as much as possible)