I need to wrap functionality in a, lets say button. However when I call the HOC in the render method of another component I get nothing.
I have this HOC
import React,{Component,PropTypes} from 'react';
export let AddComment = (ComposedComponent) => class AC extends React.Component {
render() {
return (
<div class="something">
Something...
<ComposedComponent {...this.props}/>
</div>
);
}
}
and trying to do this
import {AddComment} from '../comments/add.jsx';
var Review = React.createClass({
render: function(){
return (
<div className="container">
{AddComment(<button>Add Comment</button>,this.props)}
</div>
});
module.exports = Review;
I want AddComment to open a Dialog and submit a comments form when I click the button. I need AddComment to be available other components throughtout the app.
Is the HOC pattern correct? How can I easily accomplish this?
Thanks
To summarize really quick: What are higher-order components?
Just a fancy name for a simple concept: Simply put: A component that takes in a component and returns you back a more enhanced version of
the component.
We are essentially enhancing a component.
Accepts a function that maps owner props to a new collection of props
that are passed to the base component.
We are basically passing the props down from that BaseComponent down
to the Wrapped Component so that we can have them available in that
child component below:
Use to compose multiple higher-order components into a single
higher-order component.
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { AddComment } from '../comments/add.jsx';
const mapProps = propFunction => Component => (props) => {
return React.createFactory(Component)(propFunction(props));
};
const compose = (propFunction, ComponentContainer) => (BaseComponent) => {
return propFunction(ComponentContainer(BaseComponent));
};
const Review = AddComment(({ handleReviewToggle }) => (
<div className="container">
<ReviewButton
primaryText="Add Comment"
_onClick={handleReviewToggle}
/>
</div>
));
export default Review;
// ================================================================== //
const EnhanceReview = compose(withProps, AddComment)(Review);
const withProps = mapProps(({ ...props }) => ({ ...props }));
The AddComment Container that will have the button and the dialog itself.
export function AddComment(ComposedComponent) {
class AC extends React.Component {
constructor() {
super();
this.state = {open: false};
}
handleReviewToggle = () => {
this.setState({ open: !this.state.open })
}
render() {
return (
<ComposedComponent
{...this.props}
{...this.state}
{...{
handleReviewToggle: this.handleReviewToggle,
}}
/>
);
}
}
export default AddComment;
// ==================================================================
The ReviewButton Button that will fire an event to change state true or false.
const ReviewButton = ({ _onClick, primaryText }) => {
return (
<Button
onClick={_onClick}
>
{primaryText || 'Default Text'}
</Button>
);
};
export default ReviewButton;
// ================================================================== //
However this was all done without using a library. There's one out called recompose here: https://github.com/acdlite/recompose. I highly suggest that you try it out without a library to get a good understanding of Higher Order Components.
You should be able to answer these questions below after playing with Higher Order components:
What is a Higher Order Component?
What are the disadvantages of using HOC? What are some use cases?
How will this improve performance? And how can I use this to optimize for performance?
When is the right time to use a HOC?
Related
I have three components in my React app, all set to grey but can turn a different color when clicked, and the idea is to have the other components change back to grey whenever one of the components turns from grey to it's chosen color. Another way to phrase it is that I want only one components to show it's color at a time.
Here is the index.js for my page.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import Redlight from "./Components/Redlight";
import Yellowlight from "./Components/Yellowlight";
import Greenlight from "./Components/Greenlight";
// probably add "isActive" line here? need to
// figure out how to get the components to listen
// to eachother
function App() {
return (
<div className="App">
<Redlight />
<Yellowlight />
<Greenlight />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
And here is one of the components. All the components are written the exact same way, so consider this one to count as all three besides the obvious differences of color.
import React from "react";
class Redlight extends React.Component {
state = {
className: "Offlight"
};
handleClick = e => {
e.preventDefault();
console.log("The red light was clicked.");
const currentState = this.state.className;
this.setState({
className: !currentState
});
};
render() {
return (
<div
className={this.state.className ? "Offlight" : "Redlight"}
onClick={this.handleClick}
/>
);
}
}
export default Redlight;
So far all the components show up and change color on click, but it's the matter of getting them all to listen to each other that is really hanging me up.
Try moving your state to your root component, which in your case is App, then make App stateful and Redlight, Bluelight, and Greenlight stateless.
If you turn your App component into a stateful component, you can pass the state of the lights down to the children component while at the same time you can manage their states in the parent's component level.
For example, for your App.js, do something like:
import React, { Component } from "react";
import Redlight from "./Redlight";
import Yellowlight from "./Yellowlight";
import Greenlight from "./Greenlight";
import "./App.css";
class App extends Component {
state = {
red: "Offlight",
yellow: "Offlight",
green: "Offlight"
};
clickHandler = (light, e) => {
e.preventDefault();
console.log("The " + light + " light was clicked.");
const currentState = this.state[light];
const newState = {
red: "Offlight",
yellow: "Offlight",
green: "Offlight"
};
this.setState({
...newState,
[light]: !currentState
});
};
render() {
return (
<div className="App">
<Redlight light={this.state.red} clicked={this.clickHandler} />
<Yellowlight light={this.state.yellow} clicked={this.clickHandler} />
<Greenlight light={this.state.green} clicked={this.clickHandler} />
</div>
);
}
}
export default App;
If you see, the state in the parent is controlling the class name for the lights, and the clickHandler turns all of them off, them turns the clicked one on.
Your children components can be cleaner, like this:
Yellowlight.js
import React from "react";
class Yellowlight extends React.Component {
render() {
return (
<div
className={this.props.light ? "Offlight" : "Yellowlight"}
onClick={(e) => this.props.clicked('yellow', e)}
/>
);
}
}
export default Yellowlight;
Redlight.js
import React from "react";
class Redlight extends React.Component {
render() {
return (
<div
className={this.props.light ? "Offlight" : "Redlight"}
onClick={(e) => this.props.clicked('red', e)}
/>
);
}
}
export default Redlight;
Greenlight.js:
import React from "react";
class Greenlight extends React.Component {
render() {
return (
<div
className={this.props.light ? "Offlight" : "Greenlight"}
onClick={(e) => this.props.clicked('green', e)}
/>
);
}
}
export default Greenlight;
You can check the final code in this sandbox where I tried to replicate your problem: https://codesandbox.io/s/friendly-haibt-i4nck
I suppose you are looking for something like this
What you need, is to lift up the state in order to hold the selected color/light into it. For this example, I'm using the state hook instead of the class component approach. I've also created one <Light /> component as they share the same logic.
You can find inline comments of what is going on for each of the steps.
Let me know if you find any trouble reading or implementing it.
What you want to do is to lift the state up to a common ancestor. You can create a common ancestor to host the information that all 3 light components need. You can pass a boolean isLightOn to tell the component to switch on or off. You will also pass an event handler to allow a light component to set the light color.
const LightContainer = () => {
const [lightColor, setLightColor] = useState('');
const RED_COLOR = 'RED';
const YELLOW_COLOR = 'YELLOW';
const GREEN_COLOR = 'GREEN';
return (
<div>
<Light className="Redlight" isLightOn={lightColor === RED_COLOR} onLightSwitch={() => { setLightColor(RED_COLOR); }} />
<Light className="Yellowlight" isLightOn={lightColor === YELLOW_COLOR} onLightSwitch={() => { setLightColor(YELLOW_COLOR); }} />
<Light className="Greenlight" isLightOn={lightColor === GREEN_COLOR} onLightSwitch={() => { setLightColor(GREEN_COLOR); }} />
</div>
);
};
On your light components, we can also make it more generic since we move most of the logic to the parent component.
const Light = ({ isLightOn, onLightSwitch, className }) => {
return (
<div
className={isLightOn ? 'Offlight' : className}
onClick={onLightSwitch}
/>
);
};
Working with an array of mapped items, I am attempting to toggle class in a child component, but state change in the parent component is not passed down to the child component.
I've tried a couple different approaches (using {this.personSelectedHandler} vs. {() => {this.personSelectedHandler()} in the clicked attribute, but neither toggled class successfully. The only class toggling I'm able to do affects ALL array items rendered on the page, so there's clearly something wrong with my binding.
People.js
import React, { Component } from 'react';
import Strapi from 'strapi-sdk-javascript/build/main';
import Person from '../../components/Person/Person';
import classes from './People.module.scss';
const strapi = new Strapi('http://localhost:1337');
class People extends Component {
state = {
associates: [],
show: false
};
async componentDidMount() {
try {
const associates = await strapi.getEntries('associates');
this.setState({ associates });
}
catch (err) {
console.log(err);
}
}
personSelectedHandler = () => {
const currentState = this.state.show;
this.setState({
show: !currentState
});
};
render() {
return (
<div className={classes.People}>
{this.state.associates.map(associate => (
<Person
name={associate.name}
key={associate.id}
clicked={() => this.personSelectedHandler()} />
))}
</div>
);
}
}
export default People;
Person.js
import React from 'react';
import classes from './Person.module.scss';
const baseUrl = 'http://localhost:1337';
const person = (props) => {
let attachedClasses = [classes.Person];
if (props.show) attachedClasses = [classes.Person, classes.Active];
return (
<div className={attachedClasses.join(' ')} onClick={props.clicked}>
<img src={baseUrl + props.photo.url} alt={props.photo.name} />
<p>{props.name}</p>
</div>
);
};
export default person;
(Using React 16.5.0)
First of all, in your People.js component, change your person component to:
<Person
name={associate.name}
key={associate.id}
clicked={this.personSelectedHandler}
show={this.state.show}}/>
You were not passing the prop show and also referring to a method inside the parent class is done this way. What #Shawn suggested, because of which all classes were toggled is happening because of Event bubbling.
In your child component Person.js, if you change your onClick to :
onClick={() => props.clicked()}
The parenthesis after props.clicked executes the function there. So, in your personSelectedHandler function, you either have to use event.preventDefault() in which case, you also have to pass event like this:
onClick={(event) => props.clicked}
and that should solve all your problems.
Here's a minimal sandbox for this solution:
CodeSandBox.io
I have a Component like this:
class GlobalComponent extends React.Component{
select= (e) => {
this.props.select(); // must be implemented in another component and not parent
};
render(){
return(
<div>
{this.state.data}
<Button onClick={this.select}>Select</Button>
</div>
);
}
}
function mapStateToProps(state) {
return state.data
}
const mapDispatchToProps = (dispatch, props) => ({
select: () => dispatch(select()),
});
export default connect(mapStateToProps, mapDispatchToProps)(GlobalComponent );
I am importing this component in the root of my app - app.js like this:
<GlobalComponent />
The problem is this actionselect. I know I can pass it from global state or parent component with props (for the example):
<GlobalComponent select={this.select} />
or with mapDispatchToProps
, but the problem is that the component is defined only in one place, otherwise I need to import the GlobalComponent in almost all components.
What I want is to import the component once (somewhere in the root) and then somehow in a component that uses GlobalComponent define a function, which will NOT execute select, but implement select and it is only for the click event in the GlobalComponent:
export default class ComponentThatUsesGlobal extends React.Component {
executeWhenGlobalComponentBtnIsClicked = () => {
}
}
Is this possible and is it good approach or it is better just to import the component everywhere ?
I am using and Redux.
I have 2 component a parent component to manage the state and a lot of other things and a child component with some reactstrap buttons radio i'm trying to change the state onClick on the child buttons but I get the error: this.setState is not a function and i can't figure out what's wrong with my code =>
//Parent
import React, { Component } from 'react';
import BtnRadio from './btnToggle';
class parent extends Component {
state = {
rSelected: true,
}
onRadioBtnClick(rSelected) {
this.setState({
rSelected:rSelected
});
}
render(){
return (
<div>
<BtnToggle onRadioBtnClick={this.onRadioBtnClick} active={this.state.rSelected}/>
</div>
);
}
};
export default AddAdmin;
//Chlid
import React from 'react';
import { Button, ButtonGroup } from 'reactstrap';
const BtnRadio = (props) => {
return (
<ButtonGroup>
<Button color="light" onClick={() => props.onRadioBtnClick(true)} active={props.active === true}>Enable</Button>
<Button color="light" onClick={() => props.onRadioBtnClick(false)} active={props.active === false}>Disabled</Button>
</ButtonGroup>
);
};
export default BtnRadio;
is there someone who can point me to the right direction i guess that i forgot to bind something...
The problem is, when you're using non-anonymous functions, this gets overridden, and wont refer to the component anymore. Since you're already using class properties, the simple fix, is to keep using the arrow functions, to keep this referencing the component:
onRadioBtnClick = (rSelected) => {
this.setState({
rSelected:rSelected
});
}
See #5 in this medium article, which explains different ways of binding this to keep it referencing the component.
<BtnToggle onRadioBtnClick={() => this.onRadioBtnClick()} active={this.state.rSelected}/>
Arrow function for the rescue.
You should bind the functions your passing like so:
class parent extends Component {
state = {
rSelected: true,
}
onRadioBtnClick(rSelected) {
this.setState({
rSelected:rSelected
});
}
render(){
return (
<div>
<BtnToggle onRadioBtnClick={this.onRadioBtnClick.bind(this)} active={this.state.rSelected}/>
</div>
);
}
}
alternatively, you can bind the functions before passing them in the constructor:
class parent extends Component {
state = {
rSelected: true,
}
constructor() {
super()
this.onRadioBtnClick = this.onRadioBtnClick.bind(this)
}
onRadioBtnClick(rSelected) {
this.setState({
rSelected:rSelected
});
}
render(){
return (
<div>
<BtnToggle onRadioBtnClick={this.onRadioBtnClick} active={this.state.rSelected}/>
</div>
);
}
}
I have a seemingly trivial question about props and function components. Basically, I have a container component which renders a Modal component upon state change which is triggered by user click on a button. The modal is a stateless function component that houses some input fields which need to connect to functions living inside the container component.
My question: How can I use the functions living inside the parent component to change state while the user is interacting with form fields inside the stateless Modal component? Am I passing down props incorrectly?
Container
export default class LookupForm extends Component {
constructor(props) {
super(props);
this.state = {
showModal: false
};
}
render() {
let close = () => this.setState({ showModal: false });
return (
... // other JSX syntax
<CreateProfile fields={this.props} show={this.state.showModal} onHide={close} />
);
}
firstNameChange(e) {
Actions.firstNameChange(e.target.value);
}
};
Function (Modal) Component
const CreateProfile = ({ fields }) => {
console.log(fields);
return (
... // other JSX syntax
<Modal.Body>
<Panel>
<div className="entry-form">
<FormGroup>
<ControlLabel>First Name</ControlLabel>
<FormControl type="text"
onChange={fields.firstNameChange} placeholder="Jane"
/>
</FormGroup>
);
};
Example: say I want to call this.firstNameChange from within the Modal component. I guess the "destructuring" syntax of passing props to a function component has got me a bit confused. i.e:
const SomeComponent = ({ someProps }) = > { // ... };
You would need to pass down each prop individually for each function that you needed to call
<CreateProfile
onFirstNameChange={this.firstNameChange}
onHide={close}
show={this.state.showModal}
/>
and then in the CreateProfile component you can either do
const CreateProfile = ({onFirstNameChange, onHide, show }) => {...}
with destructuring it will assign the matching property names/values to the passed in variables. The names just have to match with the properties
or just do
const CreateProfile = (props) => {...}
and in each place call props.onHide or whatever prop you are trying to access.
I'm using react function component
In parent component first pass the props like below shown
import React, { useState } from 'react';
import './App.css';
import Todo from './components/Todo'
function App() {
const [todos, setTodos] = useState([
{
id: 1,
title: 'This is first list'
},
{
id: 2,
title: 'This is second list'
},
{
id: 3,
title: 'This is third list'
},
]);
return (
<div className="App">
<h1></h1>
<Todo todos={todos}/> //This is how i'm passing props in parent component
</div>
);
}
export default App;
Then use the props in child component like below shown
function Todo(props) {
return (
<div>
{props.todos.map(todo => { // using props in child component and looping
return (
<h1>{todo.title}</h1>
)
})}
</div>
);
}
An addition to the above answer.
If React complains about any of your passed props being undefined, then you will need to destructure those props with default values (common if passing functions, arrays or object literals) e.g.
const CreateProfile = ({
// defined as a default function
onFirstNameChange = f => f,
onHide,
// set default as `false` since it's the passed value
show = false
}) => {...}
just do this on source component
<MyDocument selectedQuestionData = {this.state.selectedQuestionAnswer} />
then do this on destination component
const MyDocument = (props) => (
console.log(props.selectedQuestionData)
);
A variation of finalfreq's answer
You can pass some props individually and all parent props if you really want (not recommended, but sometimes convenient)
<CreateProfile
{...this.props}
show={this.state.showModal}
/>
and then in the CreateProfile component you can just do
const CreateProfile = (props) => {
and destruct props individually
const {onFirstNameChange, onHide, show }=props;