render method not triggered with observable array update - reactjs

Data loading with fetch method in Repo class fine, but I couldnt pass it component, actually I expect it to re-render of observer component but its not happenning. Here they are;
MenuComponent.tsx:
#observer
#inject('params')
class MenuComponent extends React.Component<{params?:IMenuModel[]}, {}> {
render() {
//params undefined.
var menuJSX : JSX.Element[] = this.props.params ? this.props.params.map((item:IMenuModel, i:number)=>{
return (<li key={item.Id}>{item.itemName}</li>)
}):[];
return render(){...}
MenuRepo.tsx:
class MenuRepo {
#observable menuItems?: IMenuModel[];
constructor() {
this.getItems();
}
#action getItems(): void {
fetch(`..`).then((response: Response): Promise<{ value: IMenuModel[] }> => {
this.menuItems = [
{ Id: 1, itemName: 'test-item1', childItems: [] }
];//property setted here..
})
}
}
export default new MenuRepo;
App.tsx;
import Menu from './components/MenuComponent';
import menuCodes from './components/MenuRepo';
class App extends React.Component<null, null> {
render() {
return (
<div className="App">
<Menu params = {menuCodes.menuItems}/>
</div>
);
}
}
export default App;
I checked execution order, jsx render method not re-render after setting observerable field(menuItems) in fetch.

try reversing the order here...from this
#observer
#inject('params')
to this...
#inject('params')
#observer
from documentation: When using both #inject and #observer, make sure to apply them in the correct order: observer should be the inner decorator, inject the outer. There might be additional decorators in between.

Related

React HOC can't resolve import

Hi I am creating my first HOC in react and I have hit a snag. I import a Contentservice inside the class HOC and I have a simple Page class that is wrapped by the WithBackend.
When i navigate to the page component using react Route i get the get the error:
TypeError: Cannot read property 'getPage' of undefined
When i debug the code, i can see the service is available in the constructor but when it gets to the getPage method, i get the alert(id) but then it breaks on the line
this.service.getPage(id);
This is the wrapper function:
import React from "react";
import ContentService from "./ContentService";
const WithBackend = (WrappedComponent) => {
class HOC extends React.Component {
constructor() {
super();
this.service = new ContentService();
}
getPage(id) {
alert(id);
this.service.getPage(id);
}
render() {
return <WrappedComponent getPage={this.getPage} {...this.props} />;
}
}
return HOC;
};
export default WithBackend;
This is the component that is wrapped:
import React, { Component } from "react";
import WithBackend from "./WithBackend";
class PageX extends Component {
render() {
return (
<div>
<h2>Home</h2>
</div>
);
}
componentDidMount() {
this.props.getPage("123");
}
}
const Page = WithBackend(PageX);
export default Page;
This is the ContentService class:
class ContentService {
getPage(id) {
alert(id);
return "Some page";
}
}
export default ContentService;
Can anyone see what i am doing wrong please? Also I am only changing the name of my page to PageX so i can import if after it is being wrapped by the name Page. Is this necessary? I rather just keep the whole thing named page.
I would appreciate your help with this.
Add the following to your code
const WithBackend = (WrappedComponent) => {
class HOC extends React.Component {
constructor() {
super();
this.service = new ContentService();
this.getPage = this.getPage.bind(this) // <-- Add this
}
getPage(id) {
alert(id);
this.service.getPage(id);
}
render() {
return <WrappedComponent getPage={this.getPage} {...this.props} />;
}
}
return HOC;
};
I would also encourage you to read about how this binding works in javascript.
Here is a link to a blog that I liked.
You need to bind this instance to getPage, the recommended way is using arrow function:
getPage = (id) => {
alert(id);
this.service.getPage(id);
}
// Or in constructor
this.getPage = this.getPage.bind(this);
// Or in the event itself
onClick={this.getPage.bind(this)}

React/TypeScript: Creating a unified "notification" function that ties to a single component

I am trying to design my app so that all notifications tie in to a single "snackbar" style component (I'm using the material UI snackbar component) that wraps the app:
example
class App extends React.Component {
public render() {
return (
<MySnackbar >
<App />
<MySnackbar />
}
}
truncated example snackbar class:
class MySnackbar extends React.Component<object, State> {
public state = {
currentMessage: { message: "", key: 0},
open: false
};
private messageQueue = [];
public openAlert = (message: string) => {
this.queue.push({ key: new Date().getTime(), message})
if (!this.state.open) {
this.setState({ open: true });
}
}
// other class methods...
public render () {
// render component here...
}
}
I am trying to figure out how I can make it so that I can simply export a function that when called, has access to the "openAlert" function referencing the parent snackbar.
hypothetical child component:
import notificationFunction from "MySnackbar";
class Child extends React.Component {
public notifyUser = () => {
notificationFunction("Hi user!")
}
}
I know there are libraries that do this, but its important for me to understand how they work before I use one. I have seen a few examples using global state (redux, react-context), but I'm looking to avoid using global state for this.
I have tried following some guides on creating HOC patterns, but I can't seem to design something that works how I want this to work. Is what I'm trying to do even technically possible? I know that I could make this work by passing the function down to every child as a prop, but that requires adding a field to every single interface and intermediate component, and is not very DRY.
Stable React's way of doing that is Context (https://reactjs.org/docs/context.html).
interface IContext {
updateMessage: (newMessage: string) => void;
}
interface IProps {}
interface IState {
message: string;
}
const SnackbarContext = React.createContext<IContext>({
updateMessage: () => {},
});
class Snackbar extends React.Component<IProps, Partial<IState>> {
constructor(props) {
super(props);
this.state = {
message: "",
};
this.updateMessage = this.updateMessage.bind(this);
}
render() {
return (
<SnackbarContext.Provider value={{ updateMessage: this.updateMessage }}>
<div>
<strong>MESSAGE:</strong> {this.state.message}
</div>
<hr />
{this.props.children}
</SnackbarContext.Provider>
);
}
updateMessage(newMessage: string) {
this.setState({ message: newMessage });
}
}
class Child extends React.Component {
static contextType: React.Context<IContext> = SnackbarContext;
context: IContext;
constructor(props) {
super(props);
this.onButtonClick = this.onButtonClick.bind(this);
}
render() {
return (
<div>
<button onClick={this.onButtonClick}>Create message</button>
</div>
);
}
onButtonClick() {
this.context.updateMessage(`Message with random number at the end ${Math.random()}`);
}
}
<Snackbar>
<Child />
</Snackbar>
There is also hooks experiment (https://reactjs.org/docs/hooks-intro.html) which may or may not be a future.
I have tried following some guides on creating HOC patterns, but I can't seem to design something
HOC will not work here. Or all JSX.Elements will need to be wrapped in HOC. And it is more easy to pass callback function down the whole element tree instead of using HOCs.

React TS: Despite passing method through wrapper, child still can't access getPage()

I'm trying to build a fetch method that can be shared to a bunch of Reader components through a higher order component. I believe I've built the HOC right, but I'm not 100% sure.
import React, { Component } from 'react';
import base from "./firebase";
export default (ChildComponent) => {
class GetPage extends Component<{},any> {
constructor(props: any) {
super(props);
this.state = {
text: "Hii"
};
}
public getPage(page: string) {
base
.fetch(page, { context: this, })
.then(data => this.setState({ text: data }));
console.log(this.state.text)
}
public render() {
return <ChildComponent getPage={this.getPage} text={...this.state.text} {...this.props}/>;
}
}
return GetPage;
};
You can see that I'm importing the HOC on the second line , but despite this, the 'Reader' component is throwing an error that 'getPage' is no where to be found.
import * as React from "react";
import GetPage from "./fetch";
class Reader extends React.Component<{},any>{
public componentWillMount() {
this.getPage('1A1');
}
public render() {
return <div{...getPage('1A1')}>{...this.state.text}</div>;
}
}
export default (GetPage(Reader));
Inside your Reader component instead of accessing this.getpage try with this.props.getpage
and I don't understand why you are doing with following:
<div{...getPage('1A1')}>

How to include the Match object into a ReactJs component class?

I am trying to use my url as a parameter by passing the Match object into my react component class. However it is not working! What am I doing wrong here?
When I create my component as a JavaScript function it all works fine, but when I try to create my component as a JavaScript class it doesn't work.
Perhaps I am doing something wrong? How do I pass the Match object in to my class component and then use that to set my component's state?
My code:
import React, { Component } from 'react';
import axios from 'axios';
import PropTypes from 'prop-types';
class InstructorProfile extends Component {
constructor(props, {match}) {
super(props, {match});
this.state = {
instructors: [],
instructorID : match.params.instructorID
};
}
componentDidMount(){
axios.get(`/instructors`)
.then(response => {
this.setState({
instructors: response.data
});
})
.catch(error => {
console.log('Error fetching and parsing data', error);
});
}
render(){
return (
<div className="instructor-grid">
<div className="instructor-wrapper">
hi
</div>
</div>
);
}
}
export default InstructorProfile;
React-Router's Route component passes the match object to the component it wraps by default, via props. Try replacing your constructor method with the following:
constructor(props) {
super(props);
this.state = {
instructors: [],
instructorID : props.match.params.instructorID
};
}
Hope this helps.
Your constructor only receives the props object, you have to put match in it...
constructor(props) {
super(props);
let match = props.match;//← here
this.state = {
instructors: [],
instructorID : match.params.instructorID
};
}
you then have to pass that match object via props int a parent component :
// in parent component...
render(){
let match = ...;//however you get your match object upper in the hierarchy
return <InstructorProfile match={match} /*and any other thing you need to pass it*/ />;
}
for me this was not wrapping the component:
export default (withRouter(InstructorProfile))
you need to import withRouter:
import { withRouter } from 'react-router';
and then you can access match params via props:
someFunc = () => {
const { match, someOtherFunc } = this.props;
const { params } = match;
someOtherFunc(params.paramName1, params.paramName2);
};
Using match inside a component class
As stated in the react router documentation. Use this.props.match in a component class. Use ({match}) in a regular function.
Use Case:
import React, {Component} from 'react';
import {Link, Route} from 'react-router-dom';
import DogsComponent from "./DogsComponent";
export default class Pets extends Component{
render(){
return (
<div>
<Link to={this.props.match.url+"/dogs"}>Dogs</Link>
<Route path={this.props.match.path+"/dogs"} component={DogsComponent} />
</div>
)
}
}
or using render
<Route path={this.props.match.path+"/dogs"} render={()=>{
<p>You just clicked dog</p>
}} />
It just worked for me after days of research. Hope this helps.
In a functional component match gets passed in as part of props like so:
export default function MyFunc(props) {
//some code for your component here...
}
In a class component it's already passed in; you just need to refer to it like this:
`export default class YourClass extends Component {
render() {
const {match} = this.props;
console.log(match);
///other component code
}
}`

pass mobx observable data to props

Using mobx in react-typescript project. This class set observable array with fetch api:
class MenuRepo {
#observable menuItems?: IMenuModel[];//=[{Id:1,itemName:"asd",childItems:[]}];
#action getItems(): void {
fetch(`...`)
.then((response: { value: IMenuModel[] }): void => {
this.menuItems = [
{ Id: 1, itemName: 'test-item1', childItems: [] }
];
});
}
and I want to track this observable data in this component class:
#observer
class Menu extends React.Component<{params?:IMenuModel[]}, {}> {
render() {
debugger
var menuJSX : JSX.Element[] = this.props.params ? this.props.params.map((item:IMenuModel, i:number)=>{
return (<li key={item.Id}>{item.itemName}</li>)
}):[];
return (...)
but params is "undefined". I watched some tutorials about mobx&react but couldnt solve it.
and here App.tsx file:
import menuCodes from './components/Codes';
class App extends React.Component<null, null> {
render() {
return (
<div className="App">
<Menu params = {asd.menuItems}/>
</div>
);
}
}
export default App;
is asd instanceof MenuRepo? Note that in the first render menuItems will be undefined, as it only get's it's first value after the fetch has resolved, which should produce the second rendering.
Note that App should be observer as that is the one dereferencing the menuItems observable. (For more info: https://mobx.js.org/best/react.html)

Resources