Binding the List items to this.state - reactjs

I have a Recact component defined as:
export default class App extends React.Component<AppProps, Items>
The Items class is defined as:
export default class Items extends Array<Item>
and the Item is just a class with a bunch of properties.
If in render() I bind a list to my Items object directly ie.
<List items={myItems} onRenderCell={this._onRenderCell} />
the list is displayed correctly, however if I this.setState(myItems) and then try to get the list binded to the state:
<List items={this.state} onRenderCell={this._onRenderCell} />
I get the compile-time error:
Type 'Readonly' is missing the following properties from type 'any[]': [Symbol.iterator], [Symbol.unscopables]
How do I fix this?

I found an easy solution to this... I created a new type:
type ItemsState = {
items: Items
}
then changed my component to use that as a state instead: export default class App extends React.Component<AppProps, ItemsState>

First, I want to point out that React's patterns discourage inheritance and prefer composition
Moreover you are extending an Array while React expect an Object.
Another misconception is "bind a function to the state". You are binding this context, not to variables.
Finally, state must be serializable and you should put in it only objects and arrays or primitives.
Try to refactor your components following this guidelines or post complete code for a more comprehensive solution.

Related

How to use noImplicitOverride flag from TS4.3 with React class components

I've updated TS to 4.3 and tried to add flag noImplicitOverride to my tsconfig.json.
I've got a lot of issues related to overriding inside my React class components ie. render()
Is it some approach to use this flag with React?
To fix the error, add the override keyword before any methods that override the method from the base class:
class Foo extends React.Component {
override render() {
return <p>Hello, world!</p>
}
}
Note that this is not React-specific and applies in general to all classes that extend another class.
See the reference on noImplicitOverride for more information.
Alternatively, if you don’t to add override before each render(), you could use functional components instead with hooks.

What is the best way for two-way binding a UI component to an observable to reduce rendering?

We are building an MVVM application using mobx and react. Currently our best bet is to create viewmodels and UI components similar to this simplified example.
// LoginModel.ts
export class LoginModel {
#observable
public userName: string;
#observable
public password: string;
}
// LoginView.tsx
#observer
export class LoginView extends React.Component<LoginViewProps> {
public render(): JSX.Element {
return (
<div>
<input type="text" value={this.model.userName} onChange={e => this.model.userName = e.value} />
<input type="password" value={this.model.password} onChange={e => this.model.password= e.value} />
</div>
);
}
}
AFAIK in this implementation the component will entirely re-render when either userName or password changes.
What I'd like to achieve is to have our own custom TextInput component which woudl be responsible for rendering the layout, styling, receiving user input, and also to show validation errors based on model state, etc. I see two options right now.
Expose the value, onChange and for example error in the custom component and use it similarly to the example above. This case the issue is the same, each change in a single observable property would AFAIK re-render the entire "form" component. This is due to the fact that I'm not dereferencing the observable in the TextInput component but in the LoginView.
Expose something like model: any and field: string and use model[field] inside. This way I would do the dereferencing inside the TextInput and it could work fine, BUT I'd loose some strong typing and universality.
A few notes:
At first run I intentionally wouldn't like to use libraries like react-forms, etc.
Also, if anyone spots it, I intentionally wouldn't like to use the Provider and #inject pattern, I like being explicit.
Could anyone give me some ideas and suggestions about such a scenario?
There is awesome form library for mobx that is using very similar technique https://formstate.github.io, you can probably copy this pattern from it.
You can use objects instead of plain strings.
interface Str{
value:string
}
then you can use .value inside your input component.

React class change in a extend the generic State/Prop

Basically I have a ViewMaster with many many Functions that gets somewhere in a wrapper => components executed
Now I want to have a different but mostly the same View that needs some extra Functions. Now 2 states are changing its type from
interface ViewState {
something:something
...
}
to
interface NewViewState extends ViewState {
change:change
}
But how am I able to do this.
My ViewMaster looks like this
class ViewMaster extends React.Component<ViewProps,ViewState>{}
and my new View
class ViewNew extends ViewMaster
But how am I able to set a new ViewState generic?
EDIT: Thinking about it, I can simply change the interface ViewState
to
interface ViewState {
change:change|something
}
But still It would be intresting to know
There are a number of ways you can do this. Given that your base component is a React Component and it already has parameterization I would do something like this:
class ViewMaster<P = ViewProps, S = ViewState> extends React.Component<P, S>{}
and then
class ViewNew extends ViewMaster<ViewProps, NewViewState>{}

Creating keys for a generic React container

I'm working on a React component that accepts a list of data items, a function that constructs a component from the data items, and formats the items into a table. The code looks something like this:
export default class DataGrid extends React.Component{
constructor(props) {
super(props);
this.generateChild = item => this.props.template(item);
}
render () {
return tabulate (
this.props.data.map(this.generateChild),
this.props.cols
);
}
}
export function tabulate (components, cols)
{
return <table><tbody>{
collateRows (components, cols).map(cols =>
<tr>{cols.map(v =>
<td key={keyOf(v)}>{v}</td>)
}</tr>)
}</tbody></table>;
}
(where collateRows is a function that allocates the data values into a 2D array of rows and columns)
What I'm wondering is, how can I write the keyOf function referred to there: it needs to produce an identifier that is unique to each item, but remains consistent across calls for the same item. It also needs to do this with as little knowledge about the data item itself as possible, as this is intended to be a completely generic component that can be used for any reasonable data type.
What I do know is that the template property is a function that returns a React component, and that that component has a property key that contains an appropriate value. But I don't seem to be able to access that property (I get undefined and a warning that says I should provide another prop with the same value, but as I want to be able to use this with any component type, I don't see how this would work...), so what can I do instead?

In componentDidUpdate refs is undefined

I want to use Chart.js on my website. As you can see title, I'm using React.js. To use Chart.js, I need the canvas and context like this:
let context = document.getElementById('canvas').getContext('2d');
let chart = new Chart(context, ...);
so I design the component like this:
export function updateChart() {
let context = this.refs.chart.getContext('2d');
let chart = new Chart(context ,... );
...
}
export default class GraphChart extends React.Component {
constructor() {
super();
updateChart = updateChart.bind(this);
}
componentDidMount() {
updateChart();
}
render() {
return <canvas ref="chart" className="chart"></canvas>;
}
}
as you can see, I exported two things, update chart function and GraphChart class. Both will using in parent component like this:
import { updateChart } from './GraphChart';
import GraphChart from './GraphChart';
class Graph extends React.Component {
...
someKindOfAction() {
// update chart from here!
updateChart();
}
render() {
return (
<div>
<SomeOtherComponents />
<GraphChart />
</div>
);
}
}
then Parent class using exported updateChart function to update chart directly. It was working, but only first time. After unmount and mount the GraphChart component, it's refs are just empty.
Why refs is empty? And If I did wrong way, how can I get canvas context for initialize Chart.js?
Object refs is undefined, because this is not what you think it is. Try logging it.
The function you’re exporting is not bound to this of your component. Or perhaps it is, but to the last created instance of your component. You can never be sure that’s the mounted instance. And even if you are, you can not use multiple instances at the same time. So, I would dismiss this approach entirely.
Other than that, providing the function to alter some component’s state is exactly the opposite of what’s React is trying to accomplish. The very basic idea is that the component should know to render itself given some properties.
The problem you are trying to solve lies in the nature of Canvas API, which is procedural. Your goal is to bridge the gap between declarative (React) and procedural (Canvas) code.
There are some libraries which do exactly that. Have you tried react-chartjs? https://github.com/reactjs/react-chartjs
Anyways, if you’re wondering how the hell should you implement it the “React way”, the key is to declare properties your component handles (not necessarily, but preferably), and then to use component lifecycle methods (e.g. componentWillReceiveProps and others) to detect when properties change and act accordingly (perform changes to the canvas).
Hope this helps! Good luck!

Resources