I want to create a React HOC that would ideally receive two components instead of one wrapped component and toggle between them. That is, in the code below, instead of <h3>component one</h3> and <h3>component two<h3>, they would each represent child components. How would I be able to accomplish this? Some psuedo code for how I would write this HOC:
<HOC>
<ComponentOne />
<ComponentTwo />
</HOC>
<HOC
componentOne={<ComponentOne />}
componentTwo={<ComponentTwo />}
/>
hoc(componentOne, componentTwo)
class HOC extends React.Component {
constructor() {
super();
this.state = {
onClick: false,
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({onClick: !this.state.onClick});
}
render() {
return (
<div>
<button onClick={this.handleClick}>Click Me!</button>
{
this.state.onClick ?
<h3>component one</h3> :
<h3>component two</h3>
}
</div>
);
}
}
ReactDOM.render(<HOC />, app);
<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>
<div id="app"></div>
I am not sure if I understood you. Why do you need it to be HOC?
If you would pass components as props like that:
<HOC
componentOne={<ComponentOne />}
componentTwo={<ComponentTwo />}
/>
Then you would be able to access them using props.
render() {
return (
<div>
<button onClick={this.handleClick}>Click Me!</button>
{
this.state.onClick ?
this.props.componentOne :
this.props.componentTwo
}
</div>
);
}
If a component has more than one child then this.props.children will be an array.
class HOC extends React.Component {
// ... rest of code ....
render() {
const { onClick } = this.state;
const { children } = this.props;
return !onClick ? children[0] : children[1];
}
}
Then use it like so:
<HOC>
<div>Child One</div>
<div>Child Two</div>
</HOC>
Obviously this will only work with two children but you could extend it by passing an integer to <HOC> through props to tell it what child to select.
Edit
After a quick look at the docs this is a better version of what I wrote above as this.props.children is not an array, it is an opaque data structure:
class HOC extends React.Component {
// ... rest of code ...
render() {
const { onClick } = this.state;
const children = React.Children.toArray(this.props.children);
return !onClick ? children[0] : children[1];
}
}
Related
Is there a way to achieve conditionally rendered content below but instead of using {renderAuthButton()} in the return statement, I want to achieve running renderAuthButton() with onCLick instead?
class App extends Component {
// ...
render() {
let {isLoggedIn} = this.state;
const renderAuthButton = () => {
if (isLoggedIn) {
return <button>Logout</button>;
} else {
return <button>Login</button>;
}
}
return (
<div className="App">
<h1>
This is a Demo showing several ways to implement Conditional Rendering in React.
</h1>
{renderAuthButton()}
</div>
);
}
}
I don't really understand your need but to render conditionally, you can do something like that
state = {
show: false,
}
<div className="App">
<button onClick={() => this.setState((prev) => { show: !prev.show })}>Toggle</button>
{this.state.show && <MyComponent />}
</div>
I'm not completely sure what you're trying to do but this is how you would conditionally render content in react:
class App extends React.Component {
constructor(props){
super(props);
this.state = {
show: false
}
this.toggleShow = this.toggleShow.bind(this);
}
toggleShow(){
this.setState({show: ! this.state.show})
}
render(){
return (
<div>
<button onClick={this.toggleShow}>Filter Content</button>
{this.state.show ? (
<p>This content is conditionally shown</p>
) : (
<p>The content is now hidden</p>
)}
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById("root"))
<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>
<div id="root"></div>
I'm trying to understand how the code below, which is from Redux examples TODOMVC, can be written using the class notation.
The code is
const App = ({todos, actions}) => (
<div>
<Header addTodo={actions.addTodo} />
<MainSection todos={todos} actions={actions} />
</div>
I tried the following but it doesn't work, I get Warning: App(...): When calling super() inApp, make sure to pass up the same props that your component's constructor was passed.
class App extends React.Component {
constructor({todos, actions}) {
super({todos, actions});
this.todos = todos;
this.actions = actions;
}
render() {
return(
<div>
<Header addTodo={this.actions.addTodo} />
<MainSection todos={this.todos} actions={this.actions} />
</div>
)
}
}
Whatever is passed to App is props. And ({ todos, actions }) is just destructuring from props. This should work:
class App extends React.Component {
render() {
const { todos, actions } = this.props;
return(
<div>
<Header addTodo={actions.addTodo} />
<MainSection todos={todos} actions={actions} />
</div>
)
}
}
By setting this.todo = todos in constructor, you're setting an instance level property. Which means if the props changes later, Header and MainSection will not be updated.
You can simply do what React asks, pass the whole props to the superclass and get out the properties you want explicitly
class App extends React.Component {
constructor(props) {
super(props);
this.todos = props.todos;
this.actions = props.actions;
}
render() {
return(
<div>
<h1>Actions: {this.actions}</h1>
{/*<Header addTodo={this.actions.addTodo} />
<MainSection todos={this.todos} actions={this.actions} />*/}
</div>
)
}
}
ReactDOM.render(<App todos={[]} actions={'some action'} />, document.getElementById('app'))
<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="app"></div>
I have a React component MoviesGallery.js with the following configuration:
class MoviesGallery extends Component {
constructor(props) {
super(props)
this.state = { currentImage: 0 };
this.closeLightbox = this.closeLightbox.bind(this);
this.openLightbox = this.openLightbox.bind(this);
this.gotoNext = this.gotoNext.bind(this);
this.gotoPrevious = this.gotoPrevious.bind(this);
}
componentWillReceiveProps(nextProps) {
this.setState({movies_genre: nextProps.movies_genre})
}
I have rendered the component in my main App.js file like so:
class App extends Component {
render() {
return (
<MuiThemeProvider muiTheme={getMuiTheme(darkBaseTheme)}>
<div className="App">
<header className="App-header">
<h1 className="App-title">Welcome to React</h1>
<RaisedButton primary={true} label="Query" className="header_buttons"/>
<RaisedButton secondary={true} label="Reset" className="header_buttons"/>
</header>
<MoviesGallery/>
</div>
</MuiThemeProvider>
);
}
}
I want to update the props of my MoviesGallery component without recreating the component. Since I already added the componentWillReceiveProps() to MoviesGallery component, how can I make it so when 'Query' button is clicked, it will pass new props to the already rendered MoviesGallery and componentWillReceiveProps() should cause it to re-render since the state will change.
Just confused about the function that will change the props themselves on-click of the rendered MoviesGallery component.
Thanks in advance!
When a parent pass a new (value) prop to the child, the child component will call the render method automatically. There is no need to set a local state inside the child component to "store" the new prop.
Here is a small example of a Counter that receives a count prop and just displays it, while the parent App in this case will change the value in its state and pass the new value to Counter:
class Counter extends React.Component {
render() {
const { count } = this.props;
return (
<div>{count}</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
}
}
onClick = () => {
this.setState({ count: this.state.count + 1 });
}
render() {
const { count } = this.state;
return (
<div>
<Counter count={count} />
<button onClick={this.onClick}>Add to counter</button>
</div>);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<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="root"></div>
you can use the 'state' for your MovieGallery.js props because the state is an object that changes and you must your code like below :
class App extends Component {
state = {
query : null
}
myFunction(query){
this.setState({query});
}
render() {
return (
<MuiThemeProvider muiTheme={getMuiTheme(darkBaseTheme)}>
<div className="App">
<header className="App-header">
<h1 className="App-title">Welcome to React</h1>
<RaisedButton primary={true} label="Query" className="header_buttons" onClick={this.myFunction = this.myfunction.bind(this)}/>
<RaisedButton secondary={true} label="Reset" className="header_buttons"/>
</header>
<MoviesGallery newProps = {this.state.query}/>
</div>
</MuiThemeProvider>
);
}
}
i hope it helps
I'm building my first React app. I'm trying to render some Routes from react-router-dom.
From the main component I call to my api to get a json object, then I update the state. The problem is my child component doesn't re-render after I have set the new state so I don't have props in the child components. I have used some functions like forcerender and componentWillReceiveProps but still doesn't work
I'm sure it's not a big problem but I have been trying to fix this for a couple of hours and I haven't been able to make it work.
Here is my latest attempt:
class DetectorEfficiencyCalculator extends Component {
constructor(props) {
super(props);
this.state = {
detectors: []
};
axios.get(`/detectors`)
.then(res => {
const detectors = res.data;
this.setState({ detectors });
console.log('state updated')
});
}
render() {
return (
<div className="DetectorEfficiencyCalculator">
<RoutesHandler detectors={this.state.detectors}/>
</div>
);
}
}
class RoutesHandler extends Component{
constructor(props) {
super(props);
this.state = { detectors: props.detectors } ;
}
componentWillReceiveProps(nextProps) {
this.setState({detectors:nextProps.detectors})
this.forceUpdate()
}
render() {
console.log('render')
return (
<div className="RoutesHandler">
<Switch>
<Route exact path='/frontend/Detectors' component={DetectorList} detectors={this.props.detectors}/>
<Route path='/frontend/Detectors/:number' component={DetectorDetail} detectors={this.props.detectors}/>
</Switch>
</div>
);
}
}
class DetectorList extends Component {
render () {
console.log('renderList')
if (!this.props.detectors) {
return null;
}
return (
<ul>
{this.props.detectors.map(u => {
return (
<Detector
id={u.id}
name={u.name}
single={u.single}
threshold={u.threshold}
angle={u.angle}
/>
);
})}
</ul>
);
}
}
<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>
Thanks in advance for your help :)
Try something like this:
class DetectorEfficiencyCalculator extends Component {
state={detectors:[]}
componentDidMount(){
const self = this;
axios.get(`/detectors`) //do it here...
.then(res => {
const detectors = res.data;
self.setState({ detectors });
console.log('state updated')
});
}
render() {
return (
<div className="DetectorEfficiencyCalculator">
<RoutesHandler detectors={this.state.detectors}/>
</div>
);
}
}
class RoutesHandler extends Component{
render() {
console.log('render')
return (
<div className="RoutesHandler">
<Switch>
<Route exact path='/frontend/Detectors' component={DetectorList} detectors={this.props.detectors}/>
<Route path='/frontend/Detectors/:number' component={DetectorDetail} detectors={this.props.detectors}/>
</Switch>
</div>
);
}
}
class DetectorList extends Component {
render () {
console.log('renderList')
if (!this.props.detectors) {
return null;
}
return (
<ul>
{this.props.detectors.map(u => {
return (
<Detector
id={u.id}
name={u.name}
single={u.single}
threshold={u.threshold}
angle={u.angle}
/>
);
})}
</ul>
);
}
}
<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>
Basically, you dont want to do any ajax calls or db access in the constructor, this is not the right way to do it in React since the constructor can be called multiple times. Instead use the React component lifecycle method componentDidMount to initiate the api call. In addition I used a variable (self) to hold a reference to the component (this) so I can use it in the axios promise handler.
Ok, I got into the solution: The route renders a function in which I render the component and load it with the props I need.
this.RenderDetectorDetail = (props) => {
return (
<DetectorDetail detectors={this.props.detectors}/>
);
};
<Route exact path='/frontend/Detectors' render={this.RenderDetectorList} />
I'm new to React and I'm puzzled on something kind of basic.
I need to append a component to the DOM after the DOM is rendered, on a click event.
My initial attempt is as follows, and it doesn't work. But it's the best thing I've thought to try. (Apologies in advance for mixing jQuery with React.)
ParentComponent = class ParentComponent extends React.Component {
constructor () {
this.addChild = this.addChild.bind(this);
}
addChild (event) {
event.preventDefault();
$("#children-pane").append(<ChildComponent/>);
}
render () {
return (
<div className="card calculator">
<p><a href="#" onClick={this.addChild}>Add Another Child Component</a></p>
<div id="children-pane">
<ChildComponent/>
</div>
</div>
);
}
};
Hopefully it's clear what I need to do, and I hope you can help me attain an appropriate solution.
Don't use jQuery to manipulate the DOM when you're using React. React components should render a representation of what they should look like given a certain state; what DOM that translates to is taken care of by React itself.
What you want to do is store the "state which determines what gets rendered" higher up the chain, and pass it down. If you are rendering n children, that state should be "owned" by whatever contains your component. eg:
class AppComponent extends React.Component {
state = {
numChildren: 0
}
render () {
const children = [];
for (var i = 0; i < this.state.numChildren; i += 1) {
children.push(<ChildComponent key={i} number={i} />);
};
return (
<ParentComponent addChild={this.onAddChild}>
{children}
</ParentComponent>
);
}
onAddChild = () => {
this.setState({
numChildren: this.state.numChildren + 1
});
}
}
const ParentComponent = props => (
<div className="card calculator">
<p><a href="#" onClick={props.addChild}>Add Another Child Component</a></p>
<div id="children-pane">
{props.children}
</div>
</div>
);
const ChildComponent = props => <div>{"I am child " + props.number}</div>;
As #Alex McMillan mentioned, use state to dictate what should be rendered in the dom.
In the example below I have an input field and I want to add a second one when the user clicks the button, the onClick event handler calls handleAddSecondInput( ) which changes inputLinkClicked to true. I am using a ternary operator to check for the truthy state, which renders the second input field
class HealthConditions extends React.Component {
constructor(props) {
super(props);
this.state = {
inputLinkClicked: false
}
}
handleAddSecondInput() {
this.setState({
inputLinkClicked: true
})
}
render() {
return(
<main id="wrapper" className="" data-reset-cookie-tab>
<div id="content" role="main">
<div className="inner-block">
<H1Heading title="Tell us about any disabilities, illnesses or ongoing conditions"/>
<InputField label="Name of condition"
InputType="text"
InputId="id-condition"
InputName="condition"
/>
{
this.state.inputLinkClicked?
<InputField label=""
InputType="text"
InputId="id-condition2"
InputName="condition2"
/>
:
<div></div>
}
<button
type="button"
className="make-button-link"
data-add-button=""
href="#"
onClick={this.handleAddSecondInput}
>
Add a condition
</button>
<FormButton buttonLabel="Next"
handleSubmit={this.handleSubmit}
linkto={
this.state.illnessOrDisability === 'true' ?
"/404"
:
"/add-your-details"
}
/>
<BackLink backLink="/add-your-details" />
</div>
</div>
</main>
);
}
}