I have a functional component and a class object which handles some stuff.
That class, created in a component does some calculations, then calls a callback function received from component and stored as a reference in the class object.
I don't think this is a proper way of approaching this issue but I dont really have a clue on how to do this different.
I believe that its an issue of where the function was called from but thats about it.
The class:
export const myClass {
private static instance: myClass;
callbackRef:() => void;
private constructor() {
callbackRef = () : void => console.log('unset Callback');
}
static getInstance(): myClass {
if (!myClass.instance) {
myClass.instance = new myClass();
}
return myClass.instance;
}
setCallback(callbackFn: () => void) : void {
this.callbackRef = callbackFn;
}
doSth() {
this.callbackRef();
}
}
The component:
import React, { useEffect, useState } from 'react'
const MyComponent = () : JSX.Element => {
const [someState, setSomeState] = useState<boolean>(false);
const someCallback = () : void => {
console.log(someState)
}
const myclass = myClass.getInstance();
myclass.setCallback(someCallback);
myclass.doSth();
useEffect(() => {
console.log(someState);
}, []);
return(<></>);
}
console output will be
false undefined
To my understanding if MyComponent was a class component I could simply this.someCallback.bind(this)
however it's a functional component and this does not exist.
I've also tried using
const someCallback = useCallback(() : void => {
console.log(someState);
}, []);
and passing it to class instance, to no avail.
Ay help would be highly appreciated, thank you D: !
(Tips on how to handle this kind of calculations aswell ;_;)
Related
I have this class component here
class App extends React.Component {
getHowler() {
this.player.howler;
}
getDuration() {
this.player.duration();
}
getSeek() {
this.player.seek();
}
setSeek() {
this.player.seek(0.5);
}
// This sound file may not work due to cross-origin setting
render() {
return (
<ReactHowler
src="http://goldfirestudios.com/proj/howlerjs/sound.ogg"
playing={true}
ref={(ref) => (this.player = ref)}
/>
);
}
}
I want this to convert into function components. I want to use react howler seek function in function components. How can I do that? I tried useRef from react and it throws me an error.
The functionality should work like this:
every time I swap the tract it should play from the beginning
const App = () => {
const player = useRef(null)
const getHowler = () => {
player.current.howler;
}
const getDuration = () => {
player.current.duration();
}
const getSeek () => {
player.current.seek();
}
const setSeek = () => {
player.current.seek(0.5);
}
render() {
return (
<ReactHowler
src="http://goldfirestudios.com/proj/howlerjs/sound.ogg"
playing={true}
ref={player}
/>
);
}
}
You might need to return asap when player.current === null
You can also wrap the component in the Error boundary component
If you want to use complete functional component already used in production, feel free to use the package I wrote a package named atpro-howler which is howler integrated with react via react-aptor
I am not sure how to frame this question properly but I will try my best to help you understand my problem. I am little bit new to frontend so facing some difficulties. There is a controller called TableViewCotroller which is written in typescript and also includes JSX code. This controller is responsible for performing any table related operations. This controller looks like this:
import * as React from 'react';
export class TableViewController extends BaseViewController {
constructor(props: any) {
super(props);
}
protected onInsertRow = async (arg: InsertArgument) => {
//some row insertion logic
};
protected onDeleteRow = async (arg: InsertArgument) => {
//some row deletion logic
};
protected onInsertColumn = async (arg: InsertArgument) => {
//some column insertion logic
};
protected onDeleteColumn = async (arg: InsertArgument) => {
//some Column deletion logic
};
render()
{
const {//some properties} = this.props
const {//state1, state2 etc} = this.state
return (
<Table
onInsertRow={this.onInsertRow}
onDeleteRow={this.onDeleteRow}
onInsertColumn={this.onInsertColumn}
onDeleteColumn={this.onDeleteColumn}
...//some other properties like this
/>
);
}
Now I have decided to decouple some of these commands like onInsertRow, onInsertColumn etc from TableViewController and move them to TableCommandingController.
My TableCommandingController looks like this :
export class TableCommanding implements ITableCommandingController{
constructor(props: any) {
super(props);
}
protected onDeleteRow = async (arg: InsertArgument) => {
//some row deletion logic
};
protected onInsertColumn = async (arg: InsertArgument) => {
//some column insertion logic
};
protected onDeleteColumn = async (arg: InsertArgument) => {
//some Column deletion logic
};
//Some other commands
}
After creating TableCommandingController, I have added a variable in TableViewController called tableCommandingController. Something like below:
export class TableViewController extends BaseViewController {
private tableCommandingController?: TableCommandingController;
constructor(props: any) {
super(props);
}
//Rest of the class
}
Now I will initialize this tableCommandingController in async fashion. I don't want to initialize it in synchronous manner(there is some other requirement). So I created a function and I will initialize it in there. Now my TableViewController looks like this:
export class TableViewController extends BaseViewController {
private tableCommandingController?: TableCommandingController;
constructor(props: any) {
super(props);
}
protected getCommandingController = async () => {
if (this.tableCommandingController !== undefined) return this.tableCommandingController;
//Here I will lazy load TableCommandingModule using webpack magic comment
const {TableCommanding } = await import(/* webpackChunkName: "TableCommandingController" */ './TableCommandingController');
this.tableCommandingController = new TableCommanding(this.props);
return this.tableCommandingController;
}
}
Now when I have tableCommanding initialized, I wanted to use tableCommanding's onInsertRow, onInsertColumn etc functions inside render of TableViewController. But I have no idea how can I do that. I have tried something below but it was not working:
render()
{
const {//some properties} = this.props
const {//state1, state2 etc} = this.state
return (
<Table
onInsertRow={this.tableCommandingController?.onInsertRow}
onDeleteRow={this.tableCommandingController?.onDeleteRow}
onInsertColumn={this.tableCommandingController?.onInsertColumn}
onDeleteColumn={this.tableCommandingController?.onDeleteColumn}
...//some other properties like this
/>
);
}
In the above method my tableCommandingController is always uninitialized. So I know that I need to call getCommandingController() to get the initialized value of tableCommandingController. Something like below:
async render()
{
const commanding = await this.getTableCommandingController();
const {//some properties} = this.props
const {//state1, state2 etc} = this.state
return (
<Table
onInsertRow={commanding .onInsertRow}
onDeleteRow={commanding .onDeleteRow}
onInsertColumn={commanding .onInsertColumn}
onDeleteColumn={commanding .onDeleteColumn}
...//some other properties like this
/>
);
}
But I cannot make render function async. Any idea how can I do this?
Please keep your render method only to destructure props, state, any small sync operation and returning JSX. Render methods are not meant for async operations. Async operations needs to be handled through react lifecycle methods. Read up lifecycle methods here: https://reactjs.org/docs/state-and-lifecycle.html
You should ideally do this operation in componentDidMount and use state to re-render your component so that the callbacks are re-assigned. Else, without a re-render, your JSX wouldn't have the actual callback, instead would be undefined as that's what was rendered during mount.
I'm working on a React Typescript project. A very simplified version of the project is below. I'm trying to use more traditional polymorphism here where I have components returned from vanilla Typescript objects (not React components) that are rendered in the component tree. The reason I want to do this is so that I can have polymorphic classes that I change at runtime and that manage their own state and business logic.
import React, { useEffect } from "react";
class ClickCounter {
private count: number;
constructor() {
this.count = 0;
}
IncrementCount() {
this.count += 1;
}
GetCount(): number {
return this.count;
}
}
interface Operation {
HandleMouseDown(event: React.MouseEvent<HTMLDivElement>): void;
GetComponents(): JSX.Element[];
}
class ClickCounterOperation implements Operation {
private clickCounter: ClickCounter;
constructor() {
const counter: ClickCounter = new ClickCounter();
this.clickCounter = counter;
}
HandleMouseDown(_: React.MouseEvent<HTMLDivElement>): void {
this.clickCounter.IncrementCount();
}
GetComponents(): JSX.Element[] {
const count: number = this.clickCounter.GetCount();
return [<div>you have clicked {count} times</div>];
}
}
export type AppState = {
currentOperation: Operation;
};
export class App extends React.Component<{}, AppState> {
constructor(props = {}) {
super(props);
const initialOperation: Operation = new ClickCounterOperation();
this.state = {
currentOperation: initialOperation,
};
this.HandleMouseDown = this.HandleMouseDown.bind(this);
}
HandleMouseDown(event: React.MouseEvent<HTMLDivElement>) {
console.log("Dispatching mouse down event to current operation");
this.state.currentOperation.HandleMouseDown(event);
}
render() {
return (
<div className="App" onMouseDown={this.HandleMouseDown}>
{this.state.currentOperation.GetComponents()}
<div>some other stuff to show</div>
</div>
);
}
}
export default App;
In the example above everything will render initially, but not after the count is updated. This is because react has no way of knowing that the state has changed and that a rerender is needed. What I'm currently doing is forcing React to rerender by passing down a RefreshOperationState callback to the Operation object that will call the App.setState() method but this feels very ugly and I don't want to do this.
Any way to achieve this kind of traditional polymorphism with React and have non-React objects inject components into the component tree and have the components update when appropriate? I understand what I am trying to do is not following the common React patterns of using Flux/Redux and having all/most app state in a store/s, but I'd like to make this app in a less functional and more OOP pattern where objects store their own state and are called polymorphicly.
Any suggestions?
As you've noted, mixing paradigms might be more trouble than it's worth. React relies on object reference equality to handle its rendering logic. Since you're mutating objects instead of creating new ones, it will never know to update.
Another rule of React state is that it is only data (never behavior and definitely not JSX), and you're trying to use both.
You could make components which use hooks like these, and then let your parent component choose how it composes itself based on what kind of Operation you want.
const useClickCounter = () => {
const [count, setCount] = useState(0);
const incCount = setCount(count + 1);
return [incCount, count];
};
The only other thing I've done is use the observable pattern on the class objects and have a React context in between which observes them and sends the updated state into the React component. The React context provider will cause all consumers beneath it to rerender with fresh state.
public subscribe = (fn: (state) => void) => {
this.observers.push(fn);
}
private update = async () => {
// Give new state to the Context which is subscribed
this.observers.forEach(fn => fn(state));
}
PS: if you're familiar with Redux, you could start with something like this:
const ClickCounter = () => {
const value = useSelector(selectedClickCounter);
return <div>{value}</div>;
};
const operations = {
clickCounter: {
RenderComponent: ClickCounter,
onPressDispatchData: { type: "increment-counter" },
},
};
const OperationHandler = () => {
const [currentOperation, setCurrentOperation] = useState(operations.clickCounter);
return <HandleMouse {...currentOperation} />;
};
const HandleMouse = (props) => {
return (
<div className="App" onMouseDown={props.onPressDispatchData}>
{props.RenderComponent}
<div>some other stuff to show</div>
</div>
);
};
I have already read all the stack-overflow questions related to this problem, also this official react post and the preferred solutions.
It's not recommended to use componentWillReceiveProps anymore!
Before you mark this question as duplicate, please understand my specific question, I didn't see any solution for my specific problem.
What I'm trying to do is very simple:
I have component KInputRange that received the value from props and send the value out (callback) onEnter event (will send the value to server only on enter)
The props.value can randomly change (coming by websocket from the server)
My Question:
Inside my components, the <input> value attribute will get the data from props or from state?
If from props:
How can I update the value internally when the user type input data?
If from state:
How can I update the new value if the props.value has change randomly from the server?
I'm actually need to update my internal state on props change
but how to do it today, if react says that's anti-pattern?
This my code so far:
class KInputRange extends React.Component<any, any> {
constructor(props: any) {
super(props);
}
private onKeyDown(e: any): void {
//If the key is enter call to props.onEnter with the current value
}
private onChange(e: any): void {
//if user change the value- change the internal value
}
public render() {
return (
<input value={?????} type="text" onChange={(e) => this.onChange(e)} onKeyDown={(e) => this.onKeyDown(e)}/>
);
}
}
Usage:
<KInputRange value={this.state.dataFromTheServer} onEnter={(val: number) => this.kInputRangeEnterClicked(val)}/>
You can use a function component as mentioned in the post you linked here.
To update the value internally you can use React's State Hook.
Something like this:
import React, { useState } from 'react';
const KInputRange = (props) => {
const [value, setValue] = useState(props.value);
function onKeyDown(e: any): void {
//If the key is enter call to props.onEnter with the current value
}
function onChange(e: any): void {
setValue(e.target.value);
}
return (
<input value={value} type="text" onChange={(e) => this.onChange(e)} onKeyDown={(e) => this.onKeyDown(e)}/>
);
}
First, as #Atul said, you DO need to use getDerivedStateFromProps.
It's all because you need to control your component value depending on both - props and internal state.
Assuming you using flow this code should help:
// #flow
import * as React from "react";
type Properties = {
remoteValue: string,
onSubmit: (value: string) => void
};
type State = {
remoteValueMemo: string,
internalValue: string
};
class KInputRange extends React.Component<Properties, State> {
static defaultProps = {
remoteValue: "",
onSubmit: () => {}
};
state = {
remoteValueMemo: this.props.remoteValue,
internalValue: this.props.remoteValue
};
static getDerivedStateFromProps(props: Properties, state: State) {
if (state.remoteValueMemo !== props.remoteValue) {
return {
remoteValueMemo: props.remoteValue,
internalValue: props.remoteValue};
}
return null;
}
handleValueChange = (event: SyntheticEvent<HTMLInputElement>) => {
this.setState({internalValue: event.currentTarget.value});
};
handleKeyDown = (event: SyntheticKeyboardEvent<HTMLInputElement>) => {
if (event.keyCode === 13) {
this.props.onSubmit(this.state.internalValue);
}
};
render(): React.Node {
const {internalValue} = this.state;
return (
<input value={internalValue} onChange={this.handleValueChange} onKeyDown={this.handleKeyDown}/>
);
}
}
export default KInputRange;
The argument passed to useState is not used to update the state on re-render.
You should use the useEffect Hook.
import React, { useEffect, useState } from 'react';
const Test = (props) => {
const [value, setValue] = React.useState({...props.value});
useEffect(() => {
setValue(props.value);
}, [props.value])
//...
}
Similar topic here: React.useState does not reload state from props
You can use useEffect hook of react whenever you need to do something on specific props change in function component
useEffect(() => {
// You can place your logic here to run whenever value changes
},[value]);
[value] is a dependency so that whenever the value changes you useEffect hook calls
Hope above is helpful to you.
I'm trying to translate this highly useful codepen from Chris Coyier from JS to TS and running into some issues.
https://codepen.io/chriscoyier/pen/jqyWXo
Early days with Typescript and not sure what Class extend declaration to use.
I'm getting a Property or signature expected.ts(1131) on "const th = this" below.
Not sure whether it's the way I've defined the Class extend for React declaration because typically in TS declaring this const would work without the extend React call.
interface Props {
}
interface State {
}
class App extends React.Component<Props, State>{
function1 : () => { }
function2 : () => {
const th = this;
this.serverRequest =
axios.get(this.props.source).then(function(result) {
th.setState({ jobs: result.data.jobs});
})
}
}
function1 : () => { } syntax is valid for object literals, not for classes. In case it's an arrow, it should be TypeScript public property, also known as JavaScript class field.
const th = this recipe is obsolete when arrows can be used instead.
It should be:
class App extends React.Component<Props, State>{
function1 = () => { }
function2 = () => {
this.serverRequest = axios.get(this.props.source).then((result) => {
this.setState({ jobs: result.data.jobs});
})
}
...
}