React Higher Order Component Initial Props - javascript

I am creating a player library and want the React flow to go like this:
PlayerHOC -> PlaylistHOC -> FooterContainer.
The reason I want it to go in this direction is that PlayerHOC has methods on it that PlaylistHOC and FooterContainer need to access (i.e from props).
My code:
class FooterContainer extends React.Component {
render() {
return (
<div>
<div className="jp-type-footer" >
//...
</div>
</div>
);
}
}
class FooterPlayer extends React.Component {
constructor() {
super();
this.options = {
smoothPlayBar: false,
muted: true,
//...
};
}
render() {
return (
<Player {...this.options} />
);
}
};
export const PlaylistHOC = (WrappedComponent) => class extends React.Component {
constructor(props) {
super(props);
//Add a new stateClass for the extra loop option
this.stateClass = merge({
shuffled: "state-shuffled",
loopedPlaylist: "state-loop-playlist"
}, this.props.stateClass);
}
setPlaylist = () => {}
};
export const PlayerHOC = (WrappedComponent) => class extends React.Component {
constructor(props) {
super(props);
//get passed in props from FooterPlayer and PlaylistHoc
}
play = () => {}
pause = () => {}
};
const Player = PlayerHOC(PlaylistHOC(FooterContainer));
export default connect()(FooterPlayer);
I also pass in props from FooterPlayer to PlayerHOC which works fine. However, I also want to pass in default props from PlaylistHOC to PlayerHOC that will never be updated and I can't figure out how to this while also keeping this flow.
For example: const Player = PlaylistHOC(PlayerHOC(FooterContainer)); this would allow me to pass in initial props from PlaylistHOC and FooterPlayer to PlayerHOC but then I would not be able to access PlayerHOC methods by props.
How do I do this?

I would use const Player = PlaylistHOC(PlayerHOC(FooterContainer)); because a parent component can't receive props from it's children.
Looks like both PlaylistHOC and PlayerHOC are mixins, so they should inherit from the component being wrapped instead of React.Component.
I've changed a code just a little bit to be able to test it, but the key idea of it is how I've extended WrappedComponent instead of React.Component in your mixins.
class FooterContainer extends React.Component {
render() {
return (
<div>
<div className="jp-type-footer">
<button onClick={this.play.bind(this)}>Play</button>
</div>
</div>
);
}
}
class FooterPlayer extends React.Component {
constructor() {
super();
this.options = {
smoothPlayBar: false,
muted: true
//...
};
}
render() {
return (
<Player {...this.options} />
);
}
};
export const PlaylistHOC = (WrappedComponent) => class extends WrappedComponent {
constructor(props) {
super(props);
//Add a new stateClass for the extra loop option
//this.stateClass = merge({
// shuffled: "state-shuffled",
// loopedPlaylist: "state-loop-playlist"
//}, this.props.stateClass);
}
setPlaylist() {
}
};
export const PlayerHOC = (WrappedComponent) => class extends WrappedComponent {
constructor(props) {
super(props);
//get passed in props from FooterPlayer and PlaylistHoc
}
play() {
console.log('playing');
}
pause() {
}
};
const Player = PlaylistHOC(PlayerHOC(FooterContainer));
export default connect()(FooterPlayer);
By the way, try decorators for some really fancy syntax like
#PlayerlistHOC
#PlayerHOC
class FooterContainer {
}
Be warned decorators are not definitive and might change a lot.

Related

React, this.props is not defined

I know this is a very common problem and I looked into many other complaints before posting this.
I have class Parent
class Parent extends React.Component {
constructor() {
super();
this.state = {
...
};
}
....
render() {
return (
<MuiThemeProvider theme={materialTheme}>
<Child
ref={...}
groupId={this.state.groupId}
groupUniqueId={this.state.groupUniqueId} />
</MuiThemeProvider>
);
}
}
And a class Child
class Child extends React.Component {
constructor() {
super(props);
this.state = {
...
};
...
}
getUsers() {
const url = `/someurl/${this.props.groupId}`;
...
}
render() {
return (...);
}
}
export default Child;
However, in the class Child, I get an error
"Uncaught ReferenceError: props is not defined"
Is there something obvious that I am missing? Thanks!
This is happening because your this is not referencing a class. It is referring to your function. You can either use arrow functions or bind this to your function in constructor. Just add below line
constructor() {
super(props);
this.state = {
...
}
this.getUsers = this.getUsers.bind(this)
}

React JS - Passing functions for child components

I have an App component and a function 'modalToggled' inside its.
I want to pass the function to multiple child components until I get to the last one, the 'interiores' component.
Like this:
<App> -> <Coluna1> -> <MenuPrincipal> -> <Portfolio> -> <PortfolioMenu> -> <interiores>
App Component, the parent of all components:
import React, { Component } from 'react';
import Coluna1 from './Coluna1'
class App extends Component {
constructor(props) {
super(props)
this.state = {
modalOn: false
}
this.modalToggled = this.modalToggled.bind(this)
}
modalToggled = (on) => {
this.setState({modalOn: on});
}
render() {
return (
<div>
<Coluna1 onModalToggle={this.modalToggled}/>
</div>
)
}
}
export default App;
This is the 'Coluna1' the first child component. I did the same thing in the another ones: 'MenuPrincipal', 'Portfolio', 'PortfolioMenu'
class Coluna1 extends Component {
constructor(props){
super(props)
}
render() {
return (
<div>
<Header />
<MenuPrincipal onModalToggle={this.props.modalToggled} />
</div>
)
}
}
export default Coluna1
Therefore here is the last component interiores, when I click on the button there appears an error message:
TypeError: _this.props.onModalToggle is not a function
import React, { Component } from 'react'
import Modal from 'react-responsive-modal';
class Interiores extends Component {
constructor(props) {
super(props)
this.state = {
open: false
}
}
onOpenModal = () => {
this.setState({ open: true });
this.props.onModalToggle(true);
};
onCloseModal = () => {
this.setState({ open: false });
this.props.onModalToggle(false);
};
render() {
const { open } = this.state;
return (
<div>
<button onClick={this.onOpenModal}>Open modal</button>
<Modal open={open} onClose={this.onCloseModal} center></Modal>
</div>
)
}
}
export default Interiores;
Does anybody know how to solve it? Thank you
It happens, because in App class you pass prop with name onModalToggle:
<Coluna1 onModalToggle={this.modalToggled}/>
But in Coluna1 you receive this props with wrong name, modalToggled:
<MenuPrincipal onModalToggle={this.props.modalToggled} />
Just make the names of props equal. In Coluna1 and other intermediate components pass and receive this props as onModalToggle:
<MenuPrincipal onModalToggle={this.props.onModalToggle} />
This is the problem
modalToggled = (on) => {
this.setState({modalOn: on});
}
Since this is a class function it needs to be defined like
modalToggled(on) {
this.setState({modalOn: on});
}

React 16.3 class method vs constructor method

I'm learning React 16.3, and it's new Context API. In particular Updating Context from a Nested Component. In their example they set a method that is defined in the constructor rather than a standard method.
class App extends React.Component {
constructor(props) {
super(props);
// What is the benefit of doing this here?
this.toggleTheme = () => {
this.setState(state => ({
theme:
state.theme === themes.dark
? themes.light
: themes.dark,
}));
};
this.state = {
theme: themes.light,
toggleTheme: this.toggleTheme,
};
}
render() {
// The entire state is passed to the provider
return (
<ThemeContext.Provider value={this.state}>
<Content />
</ThemeContext.Provider>
);
}
}
Everything I've read regarding lifting state up and passing methods down to children has been done using the below pattern. Why is the above preferred over the below? Are there any differences?
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
theme: themes.light,
toggleTheme: this.toggleTheme,
};
this.toggleTheme = this.toggleTheme.bind(this);
}
// Could it be done here?
toggleTheme() {
this.setState(state => ({
theme:
state.theme === themes.dark
? themes.light
: themes.dark,
}));
};
render() {
// The entire state is passed to the provider
return (
<ThemeContext.Provider value={this.state}>
<Content />
</ThemeContext.Provider>
);
}
}
If you use the first approach which is defining the method inside the constructor like this
constructor() {
this.toggleTheme = () => {
this.setState(state => ({
theme:
state.theme === themes.dark
? themes.light
: themes.dark,
}));
};
}
Then when your component usesthis.toggleTheme as a callback, you don't have to bind its this reference to the current component in which it is defined, e.g. this.toggleTheme = this.toggleTheme.bind(this), on the other hand, if you define toggleTheme as a method outside the constructor as in your second example, and if toggleTheme is passed as a callback, you will get "setState is not defined" or something like that when toggleTheme is invoked
Also, with the first approach toggleTheme is added as a instance property to the component class meaning each component instance will have a separate copy of toggleTheme, whereas the second approach will add it to the prototype of the component class which is better in terms of memory consumption because all component instances will share that method on the prototype
The difference between this two approaches:
class MyComponenet extends React.Component {
constructor(props) {
super(props);
this.method = () => {
console.log('Method');
}
}
render() {
return null;
}
}
... and ...
class MyComponenet extends React.Component {
method() {
console.log('Method');
}
render() {
return null;
}
}
Is that the first approach defines the method with the arrow notation which automatically binds the function's this to be the instance of the component class while the other doesn't.
You could change the second example to:
class MyComponenet extends React.Component {
method = () => {
console.log('Method');
}
render() {
return null;
}
}
This would be the same as the first example, but keep in mind you have to enable your transpiler option that allows this syntax.

Javascript export a new object instance

export utilities = {
assignOptions: function(newOption, callback) {
//Incorrect 'this' context on second binding
this.props.updateOptions((prevOptions) => Object.assign({}, prevOptions, newOption), callback);
}
};
export const FirstComponent = (WrappedComponent) => class extends React.Component {
constructor(props) {
super(props);
utilities.assignOptions = utilities.assignOptions.bind(this);
utilities.assignOptions("test"); //'this' context is FirstComponent
}
};
export const SecondComponent = (WrappedComponent) => class extends React.Component {
constructor(props) {
super(props);
utilities.assignOptions = utilities.assignOptions.bind(this);
utilities.assignOptions("test2"); //'this' context is still FirstComponent instead of SecondComponent
}
};
It seems to be the same instance that's getting exported and so the bindings aren't working.
How do I export a new instance each time?
The issue is likely how JS handles context within objects vs ES6 classes. An easy way to do prop injection like what I think you're attempting is to create an HOC that will wrap your components:
function wrapComponent(ComponentToWrap) {
return class WrappedComponent extends React.Component {
render() {
return <ComponentToWrap {...this.props}/>
}
}
}
This is a simplistic example but think that you can use as much logic as you'd like to inject whatever props you'd like into each WrappedComponent.

React - props is empty when calling a callback function from child

I have a button on my main component, when its clicked its open an "Approval pannel", And when the OK is clicked I am calling a callback function to the main component and doing some logic.
I want to pass the callback function(My reasons), The problem is that when the callback function is called, the props and state are undefined.
Why is that happening? Please tell me if any info is missing.
I have added a partial code here:
class MainComponent extends React.Component {
constructor(props){
currentActionConfig = {onOkClick: this.onGenericApprovalOkClicked, ...};
}
onCommandApprovalOkClicked(commandText){
console.log(this.props); <- 'undefined'
}
render(){
return <ActionsApprovalPanel currentActionConfig={this.currentActionConfig}/>
}
}
export default class ActionsApprovalPanel extends React.Component {
render()
{
...
return <ChangeIpApproval onOkClick={this.props.currentActionConfig.onOkClick}/>;
...
}
}
Try these changes
class MainComponent extends React.Component {
constructor(props){
super(props); //1. Call super
this.currentActionConfig = {onOkClick: this.onGenericApprovalOkClicked.bind(this), ...}; // 2.bind this
}
onCommandApprovalOkClicked(commandText){
console.log(this.props); <- 'undefined'
}
render(){
return <ActionsApprovalPanel currentActionConfig={this.currentActionConfig}/>
}
}
export default class ActionsApprovalPanel extends React.Component {
render()
{
...
return <ChangeIpApproval onOkClick={this.props.currentActionConfig.onOkClick}/>;
...
}
}
I think you need to make few changes to your React component.
First: In the constructor call super().
Second:: Define currentActionConfig as a state and try using it as this.state.currentActionConfig
Third: Specify the binding on onCommandApprovalOkClicked(). as
onCommandApprovalOkClicked = (commandText) => {} and similary for other functions.
class MainComponent extends React.Component {
constructor(props){
super(props);
this.state = {
currentActionConfig = {onOkClick: this.onGenericApprovalOkClicked, ...}
};
}
onCommandApprovalOkClicked(commandText){
console.log(this.props); <- 'undefined'
}
render(){
return <ActionsApprovalPanel currentActionConfig={this.state.currentActionConfig}/>
}
}
export default class ActionsApprovalPanel extends React.Component {
render()
{
...
return <ChangeIpApproval onOkClick={this.props.currentActionConfig.onOkClick}/>;
...
}
}
Make these changes and see if they work.

Categories

Resources