React typescript extending a container - javascript

I'm trying to create a base class, wrap that in a container, and then extending this within my other components.
Here is a simplified example:
let state = new State();
class Base extends React.Component<{}, {}> {
}
const Container = state.connect(
() => {
return {
}
},
() => {
return {
}
}
)(Base);
class S extends Container {
}
This, however, is returning an error:
`error TS2507: Type 'any' is not a constructor function type.`.
If I extend the Base directly, it works fine, but then how would I get access to the state and dispatch actions that I'd put into the container?
UPDATE
I created the following interface (omitting templates for simplicity)
interface IState {
connect: (
mapStateToProps: () => any,
mapDispatchToProps: () => any,
) => (component: typeof React.Component) => typeof React.Component
}
let state: IState = new State();
Now class extension does not throw any error. However, the original Base class is never called! Only the constructor of the Connect method is called.
The idea here is that I will have an abstract component and container (all the dispatch actions are on the container). And I can then extend this component from my other classes. However, since the container contains all the dispatch actions, then I need to extend the container, not the component.

I think you can reuse the value returned by state.connect().
let state = new State();
class Base extends React.Component<{}, {}> {
}
class S extends Base {
}
const containerFactory = state.connect(
() => {
return {}
},
() => {
return {}
}
);
const BaseContainer = containerFactory(Base);
const SContainer = containerFactory(S);

Related

React Component Inheritance to use parent method and child method

Background
I created a fully functional component. The component has state and props, and there are many methods inside. My component should work differently according to the os(ios / android). So I solved this problem by if statement like below.
if( platform.os == 'ios') { ... } else { ... }
The problem was that as the code volume increased, there was a problem with readability, and I decided to make a separate component for IOS and for Android. The first thing that came to mind was inheritance because ES6 and Typescript Support Class. The picture of concept is this.
However, React does not recommend inheritance. So I was just going to hand over the functions overridden by props to the Speech component in the SpeechIOS component's render function.
The code is as follows.
Speech.tsx
type Props = {
team: number,
onSpeechResults: (result: string) => void
...
}
type States = {
active: boolean;
error: string;
result: string;
...
};
export default class Speech extends Component<Props,States> {
state = { ... };
constructor(props: Props) {
super(props);
...
}
// render
render() {
...
return (
<ImageBackground source={require("../images/default-background.jpeg")} style={styles.full}>
...
</ImageBackground>
);
}
sendCommand = (code: number, speed: number, callback?: () => void) => { ... }
getMatchedSpell = (spellWord: string): { code: number, speed: number } => { ... }
onSpeechResults(e: Voice.Results) { ... };
...
}
SpeechIOS.tsx
import Speech from './Speech';
type Props = {}
type States = {}
export default class SpeechIOS extends Component<Props,States> {
constructor(props: Props) {
super(props);
...
}
// render
render() {
...
return ( <Speech team="1" onSpeechResults={this.onSpeechResults}></Speech> );
}
sayHello() {
console.log("Hello!!");
}
// I want that Speech Component call this onSpeechResults function
onSpeechResults(result: string) {
this.setState({...});
let temp = this.getMatchedSpell( ... ); // which is in Speech Component
this.sendCommand( 10, 100 ... ); // which is in Speech Component
this.sayHello(); // which is in SpeechIOS component only
... other things..
};
}
Problem.
As you can see, the onSpeechResults which is in SpeechIOS Component use some functions in Speech Component and in SpeechIOS Component also.
So, How to solve this problem? Should I use Inheritance?
Alternatively, break out any common logic into a utility function like SpeechUtil.ts, a whole new file that holds this shared logic and each util function, and exports them. Then, each component separately imports them. That ensures that if you ever update that logic it affects both components
You should define a top level component that defines the shared props and methods, and use those to render either your ios or android component. pass the props and methods to the children. This is composition which is favored over inheritance in react. example:
class Speech extends React.Component {
state ={shared: 'state'}
sharedMethod = () => {
this.setState({blah: 'blah})
}
render() {
const Root = Platform.select({
ios: SpeechIOS,
android: SpeechAndroid
})
return <Root onClick={this.sharedMethod} shared={this.state.shared}/>
}
}
You can use React.createRef for this purpose.
Below is an example code.
import Speech from './Speech';
type Props = {}
type States = {}
export default class SpeechIOS extends Component<Props, States> {
constructor(props: Props) {
super(props);
this.speech = React.createRef();
}
// render
render() {
...
return (<Speech ref={this.speech} team="1" onSpeechResults={this.onSpeechResults}></Speech>);
}
sayHello() {
console.log("Hello!!");
}
// I want that Speech Component call this onSpeechResults function
onSpeechResults(result: string) {
this.setState({ ...});
let temp = this.speech.current.getMatchedSpell(... ); // which is in Speech Component
this.sendCommand(10, 100 ... ); // which is in Speech Component
this.sayHello(); // which is in SpeechIOS component only
...other things..
};
}

Change props value outsite react and reload component

As the title says, I want to change value of props and reload component in external js file.
<div data-group=""></div>
//external.js
const popupChat = document.getElementById('popupChatComponent');
popupChat.setAttribute('data-group', groupId);
//component.ts
export default class PopupChatRoot extends React.Component {
private readonly groupId: string;
constructor(props) {
super(props);
this.groupId = this.props.group;
}
render() {
return (
<div className="modal-body">
<p>{this.groupId}</p>
</div>
);
}
}
const component = document.getElementById('popupChatComponent');
if (component) {
const props = Object.assign({}, component!.dataset);
render(<PopupChatRoot {...props}/>, component);
}
How I can do this ?
What you can do is use a wrapper component or higher order component which provides those props to your component, and have that have that wrapper component integrated with your external javascript code.
Here is an HOC I use to do something similar:
export interface MyHocProps {
//the props you want to provide to your component
myProp: any;
}
export const withMyHOC = <T extends any>(params: any) =>
<P extends MyHocProps>(WrappedComponent: React.ComponentType<P>): React.ComponentClass<defs.Omit<P, keyof MyHocProps>> => {
return class extends React.PureComponent<defs.Omit<P, keyof MyHocProps>> {
//here you have access to params, which can contain anything you want
// maybe you can provide some sort of observable which causes this to re-render
render() {
return <WrappedComponent
{...this.props}
myProp={/*whatever*/}
/>;
}
}
};
From here, you would integrate this HOC with some kind of system to push changes to it. I recommend using an observable. Basically you want to have this HOC component subscribe to changes in some piece of observable data, and then force itself to re-render when it changes.
Alternatively, you can just expose some method on your component if it is just a singleton by doing something like window.reloadThisComponent = this.reload.bind(this);, but that should probably be considered a last resort.
It is just a generic example, it might help you to solve your problem. Actually I don't think you can change props of the root node.
// yourEventService.js
class YourEventService {
listeners = []
subscribe = listener => this.listeners.push(listener)
unsubscribe = listener => this.listeners = this.listeners.filter(item => item !== listener)
emit = message => listener.forEach(listener => listener(message))
}
export default new YourEventService() // singleton export
// someWhereElse.js
import yourEventService from './yourEventService'
window.addEventListener('click', () => yourEventService.emit('myNewGroup')) // it's just an event example
//component.js, sorry I don't know how to typescript well
import yourEventService from './yourEventService'
export default class PopupChatRoot extends React.Component {
state = {
groupId: this.props.group; // initial value is passed by props
}
componentDidMount() {
yourEventService.subscribe(this.handleMessage)
}
componentWillUnmount() {
yourEventService.unsubscribe(this.handleMessage)
}
handleMessage = message => {
this.setState({ groupId: message })
}
render() {
return (
<div className="modal-body">
<p>{this.state.groupId}</p>
</div>
);
}
}
const component = document.getElementById('popupChatComponent');
if (component) {
const props = Object.assign({}, component.dataset);
render(<PopupChatRoot {...props}/>, component);
}

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.

Move function in React from component to referenced library

I'm learning React and I'm not sure how to setup this pattern. It could be something really easy I'm just missing.
I have a main component that controls state. It has all of the functions to update state and passes these down to child components via props. I've simplified the code to focus on one of these functions.
Here's the component now, all works as it should:
ManageMenu.js
import React from 'react'
class ManageMenu extends React.Component {
constructor() {
super()
this.toggleEditing = this.toggleEditing.bind(this)
// Set initial state
this.state = {
menuSections: []
}
}
toggleEditing(id) {
const menuSections = this.state.menuSections
menuSections.map(key => (key.id === id ? key.details.editing = id : ''))
this.setState({ menuSections })
}
render() {
return (
...
)
}
}
export default ManageMenu
The toggleEditing is passed via props to a child component that uses it to render an editing form if the edit button is clicked.
I have about 10 of these different functions in this component and what I would like to do is move them to an external lib/methods.js file and then reference them. Below is the code I would like to have, or something similar, but React doesn't like what I'm doing. Throws a syntax error:
Failed to compile.
Error in ./src/components/ManageMenu.js
Syntax error: Unexpected token
toggleEditing(id, menuSectionId, this.state, this)
Here is what I would like to do...
lib/methods.js
const toggleEditing = function(id, state, that) {
const menuSections = state.menuSections
menuSections.map(key => (key.id === id ? key.details.editing = id : ''))
that.setState({ menuSections })
}
module.exports = {
toggleEditing
}
And then in my component:
ManageMenu.js
import React from 'react'
import { toggleEditing } from '../lib/methods'
class ManageMenu extends React.Component {
constructor() {
super()
// Set initial state
this.state = {
menuSections: []
}
}
toggleEditing(id, this.state, this)
render() {
return (
...
)
}
}
export default ManageMenu
Any help is appreciated, thanks!
Thanks to #Nocebo, the answer on how to externalize functions is here:
Externalise common functions in various react components
In my particular situation,
I need to remove the “floating” toggleEditing(id, this.state, this) call in the middle of nowhere. Update: This error happens “because it is invoking a method within a class definition.” (see Pineda’s comment below)
Remove the leading this. on the right side of the this.toggleEditing statement in constructor()
Update the function in lib/methods.js to remove the state and that variables since its bound to this in the constructor()
See updated code below.
ManageMenu.js
import React from 'react'
import { toggleEditing } from '../lib/methods'
class ManageMenu extends React.Component {
constructor() {
super()
this.toggleEditing = toggleEditing.bind(this)
// Set initial state
this.state = {
menuSections: []
}
}
render() {
return (
...
)
}
}
export default ManageMenu
lib/methods.js
const toggleEditing = function(id) {
const menuSections = this.state.menuSections
menuSections.map(key => (key.id === id ? key.details.editing = id : ''))
this.setState({ menuSections })
}
module.exports = {
toggleEditing
}
You're error arises because you are invoking toggleEditing in your ManageMenu.js class definition rather than defining a function.
You can achive what you want by setting a local class member this.toggleEditing to the bound function returned by the .bind method and do so within the constructor:
import React from 'react'
import { toggleEditing } from '../lib/methods'
class ManageMenu extends React.Component {
constructor() {
super()
this.state = {
menuSections: []
}
// bind external function to local instance here here
this.toggleEditing = toggleEditing.bind(this);
}
// don't invoke it here, bind it in constructor
//toggleEditing(id, this.state, this)
render() {
return (
...
)
}
}
export default ManageMenu

Reactjs, parent component, state and props

I m actually learning reactjs and I m actually developping a little TODO list, wrapped inside of a "parent component" called TODO.
Inside of this parent, I want to get the current state of the TODO from the concerned store, and then pass this state to child component as property.
The problem is that I dont know where to initialize my parent state values.
In fact, I m using ES6 syntax, and so, I dont have getInitialState() function. It's written in the documentation that I should use component constructor to initialize these state values.
The fact is that if I want to initialize the state inside of my constructor, the this.context (Fluxible Context) is undefined actually.
I decided to move the initialization inside of componentDidMount, but it seems to be an anti pattern, and I need another solution. Can you help me ?
Here's my actual code :
import React from 'react';
import TodoTable from './TodoTable';
import ListStore from '../stores/ListStore';
class Todo extends React.Component {
constructor(props){
super(props);
this.state = {listItem:[]};
this._onStoreChange = this._onStoreChange.bind(this);
}
static contextTypes = {
executeAction: React.PropTypes.func.isRequired,
getStore: React.PropTypes.func.isRequired
};
componentDidMount() {
this.setState(this.getStoreState()); // this is what I need to move inside of the constructor
this.context.getStore(ListStore).addChangeListener(this._onStoreChange);
}
componentWillUnmount() {
this.context.getStore(ListStore).removeChangeListener(this._onStoreChange);
}
_onStoreChange () {
this.setState(this.getStoreState());
}
getStoreState() {
return {
listItem: this.context.getStore(ListStore).getItems() // gives undefined
}
}
add(e){
this.context.executeAction(function (actionContext, payload, done) {
actionContext.dispatch('ADD_ITEM', {name:'toto', key:new Date().getTime()});
});
}
render() {
return (
<div>
<button className='waves-effect waves-light btn' onClick={this.add.bind(this)}>Add</button>
<TodoTable listItems={this.state.listItem}></TodoTable>
</div>
);
}
}
export default Todo;
As a Fluxible user you should benefit from Fluxible addons:
connectToStores.
The following example will listen to changes in FooStore and BarStore and pass foo and bar as props to the Component when it is instantiated.
class Component extends React.Component {
render() {
return (
<ul>
<li>{this.props.foo}</li>
<li>{this.props.bar}</li>
</ul>
);
}
}
Component = connectToStores(Component, [FooStore, BarStore], (context, props) => ({
foo: context.getStore(FooStore).getFoo(),
bar: context.getStore(BarStore).getBar()
}));
export default Component;
Look into fluxible example for more details. Code exсerpt:
var connectToStores = require('fluxible-addons-react/connectToStores');
var TodoStore = require('../stores/TodoStore');
...
TodoApp = connectToStores(TodoApp, [TodoStore], function (context, props) {
return {
items: context.getStore(TodoStore).getAll()
};
});
As a result you wouldn't need to call setState, all store data will be in component's props.

Categories

Resources