How to import tsx into a SPFx extension - reactjs

I have just started my first SharePoint project and cannot figure out how to use my React components in my extension. Here are the relevant files.
Navbar.tsx:
import * as React from "react";
export const Navbar = props => <div>Hello world</div>;
ReactSharePointNavbarApplicationCustomizer.tsx:
import { override } from "#microsoft/decorators";
import { Log } from "#microsoft/sp-core-library";
import {
BaseApplicationCustomizer,
PlaceholderContent,
PlaceholderName
} from "#microsoft/sp-application-base";
import { Dialog } from "#microsoft/sp-dialog";
import * as strings from "ReactSharePointNavbarApplicationCustomizerStrings";
import styles from "./AppCustomizer.module.scss";
import { escape } from "#microsoft/sp-lodash-subset";
import * as Components from "./components";
import Navbar = Components.Navbar;
const LOG_SOURCE: string = "ReactSharePointNavbarApplicationCustomizer";
/**
* If your command set uses the ClientSideComponentProperties JSON input,
* it will be deserialized into the BaseExtension.properties object.
* You can define an interface to describe it.
*/
export interface IReactSharePointNavbarApplicationCustomizerProperties {}
/** A Custom Action which can be run during execution of a Client Side Application */
export default class ReactSharePointNavbarApplicationCustomizer extends BaseApplicationCustomizer<
IReactSharePointNavbarApplicationCustomizerProperties
> {
private _onDispose(): void {
console.log("No place holder.");
}
private _topPlaceholder: PlaceholderContent | undefined;
private _renderPlaceHolders(): void {
if (!this._topPlaceholder) {
this._topPlaceholder = this.context.placeholderProvider.tryCreateContent(
PlaceholderName.Top,
{ onDispose: this._onDispose }
);
if (!this._topPlaceholder) {
return;
}
if (this.properties) {
const Nav = Navbar(null);
if (this._topPlaceholder.domElement) {
this._topPlaceholder.domElement.innerHTML = `
<div class="${styles.app}">
<div class="ms-bgColor-themeDark ms-fontColor-white ${
styles.top
}">
${Nav}
${Navbar}
<div>Hello</div>
<Navbar/>
</div>
</div>`;
}
}
}
}
#override
public onInit(): Promise<void> {
Log.info(LOG_SOURCE, `Initialized ${strings.Title}`);
// Added to handle possible changes on the existence of placeholders.
this.context.placeholderProvider.changedEvent.add(
this,
this._renderPlaceHolders
);
// Call render method for generating the HTML elements.
this._renderPlaceHolders();
return Promise.resolve<void>();
}
}
components:
export * from "./Navbar";
My goal is to use my react component as a navigation bar, however I cannot manage to combine tsx and ts in this context.
I followed this guide: https://learn.microsoft.com/en-us/sharepoint/dev/spfx/extensions/get-started/using-page-placeholder-with-extensions
Outside of these files, the only modifications I made were to add a components folder, with the component and index you see above.
Please help me solve this challenge.

After working on this for a few hours, I have found the solution. I was coming at this the wrong way, I needed to use ReactDOM to insert my TSX components. Afterward it was normal React development. No need to try to insert elements in some fancy way as I was doing before.
Here is the working code.
ReactSharePointNavbarApplicationCustomizer.ts:
import * as React from "react";
import * as ReactDOM from "react-dom";
import { override } from "#microsoft/decorators";
import { Log } from "#microsoft/sp-core-library";
import {
BaseApplicationCustomizer,
PlaceholderContent,
PlaceholderName
} from "#microsoft/sp-application-base";
import { Dialog } from "#microsoft/sp-dialog";
import * as strings from "ReactSharePointNavbarApplicationCustomizerStrings";
import styles from "./AppCustomizer.module.scss";
import { escape } from "#microsoft/sp-lodash-subset";
import Navbar, { INavbarProps } from "./components/Navbar";
const LOG_SOURCE: string = "ReactSharePointNavbarApplicationCustomizer";
export interface IReactSharePointNavbarApplicationCustomizerProperties {}
export default class ReactSharePointNavbarApplicationCustomizer extends BaseApplicationCustomizer<
IReactSharePointNavbarApplicationCustomizerProperties
> {
private _onDispose(): void {}
private onRender(): void {
const header: PlaceholderContent = this.context.placeholderProvider.tryCreateContent(
PlaceholderName.Top,
{
onDispose: this._onDispose
}
);
if (!header) {
Log.error(LOG_SOURCE, new Error("Could not find placeholder PageHeader"));
return;
}
const elem: React.ReactElement<INavbarProps> = React.createElement(Navbar);
ReactDOM.render(elem, header.domElement);
}
#override
public onInit(): Promise<void> {
this.onRender();
return Promise.resolve<void>();
}
}
Navbar.tsx:
import * as React from "react";
import styles from "./Navbar.module.scss";
import NavbarItem from "../NavbarItem";
export interface INavbarProps {}
export default class Navbar extends React.Component<INavbarProps> {
constructor(props: INavbarProps) {
super(props);
}
public render(): JSX.Element {
return (
<div className={"ms-bgColor-themeDark ms-fontColor-white " + styles.nav}>
Hello world
</div>
);
}
}
As you can see, the components.ts export file was unnecessary. And I am sure other code may still be useless in these examples.
I found that importing tsx components into other tsx components works like normal React imports. Just import and insert as an element.

Related

enzyme is not recognizing lodash.flowright

I trying to test a React component which renders a D3 element in another class, below is the component code.
import React, {Component} from 'react';
import {graphql} from "react-apollo";
import * as compose from 'lodash.flowright';
import MostConvD3bar from './MostConvD3bar'
import MostConvProdQuery from "../../Query/MostConvProdQuery";
import style from "./MostConvProds.css"
class MostConvProds extends Component {
componentDidMount(){
if (!this.props.loading)
new MostConvD3bar(this.refs.mostConChart, this.props.products)
}
componentWillReceiveProps(nextProps){
if (!nextProps.loading)
new MostConvD3bar(this.refs.mostConChart, nextProps.products)
}
render() {
return (
<div className={style.container} ref="mostConChart"> </div>
)
}
}
const mostConvQuery = {
name: "mostConvQuery",
props({ mostConvQuery: { error, loading, mostConverted } }) {
return {
loading,
error,
products: mostConverted
}
}
}
export default compose(
graphql(MostConvProdQuery, mostConvQuery))(MostConvProds);
I have imported lodash.flowright as compose as you can see, but I am getting an error like below.
TypeError: compose is not a function
Any help will be appreciated.
Replace:
import * as compose from 'lodash.flowright';
with:
import { flowRight as compose } from 'lodash';

How to avoid repeating interface in Typescript React

It's my first app I try to build using Typescript. I want to keep styles and components in separate files to make the code more descriptive and clear. Project will consist of dozens of components and I'll use props to call the classes. Each component will look more or less like this:
import * as React from 'react'
import withStyles from "#material-ui/core/styles/withStyles"
import { LandingPageStyles } from "./landing-page-styles"
interface LandingPageProps {
classes: any
}
class LandingPage extends React.Component<LandingPageProps> {
get classes() {
return this.props.classes;
}
render() {
return(
<div className={this.classes.mainPage}>
Hello Typescript
</div>
)
}
}
export default withStyles(LandingPageStyles)(LandingPage)
And simplified styles module :
import { createStyles } from "#material-ui/core";
export const LandingPageStyles = () => createStyles({
mainPage: {
textAlign: "center",
minHeight: "100vh",
}
})
In every component I want to have the classes props with type of any. Is there a way to avoid declaring interface for each component? It works now but I don't like my current solution beacuse of repetition the same code in every single component.
The proper way to do it is as bellow. Material-ui expose WithStyles interface that you can inherit to include classes props. The main advantage is that you IDE will handle autocompletion for the defined jss class. But anyway Typescript is more verbose than Javacript. With React you often have to repeat obvious things.
import * as React from 'react'
import {withStyles, WithStyles} from "#material-ui/core"
import { LandingPageStyles } from "./landing-page-styles"
interface LandingPageProps extends WithStyles<typeof LandingPageStyles> {
}
class LandingPage extends React.Component<LandingPageProps> {
get classes() {
return this.props.classes;
}
render() {
return(
<div className={this.classes.mainPage}>
Hello Typescript
</div>
)
}
}
export default withStyles(LandingPageStyles)(LandingPage)
The best solution is to declare interface which extends WithStyles . So in component there is need to declare:
import * as React from 'react'
import withStyles, { WithStyles } from "#material-ui/core/styles/withStyles"
import { LandingPageStyles } from "./landing-page-styles"
interface LandingPageProps extends WithStyles<typeof LandingPageStyles>{
}
class LandingPage extends React.Component<LandingPageProps> {
get classes() {
return this.props.classes;
}
render() {
return(
<div className={this.classes.mainPage}>
Hello Typescript
</div>
)
}
}
export default withStyles(LandingPageStyles)(LandingPage)

How to use 'Inject' without decorator in Mobx with ReactJs

I wonder how to use Inject without decorator.
First, I made this as Store.
//Data/imagedb
import { decorate, observable } from 'mobx';
class imageStore {
list = {
keyword : 'yoyo',
}
}
decorate(imageStore, {
list: observable,
})
export default imageStore;
Second, I made this code to inject Store on it.
//components/Image
import React, { Component } from 'react';
import { observer, inject } from 'mobx-react';
class Image extends Component {
render() {
const onPut2 = {onPut};
return (
<div>
{onPut2}
</div>
);
}
}
export default inject(({ imSt }) => ({
onPut: imSt.list.keyword,
}))(observer(Image));
And finally, this is my last code.
The error is "Onput is not defined" in second code.
import React from 'react';
import Image from 'components/Image';
import { Provider } from 'mobx-react';
import imageStore from 'Data/imagedb'
const imSt = new imageStore();
const Home = () => {
return (
<div>
<Provider imSt={imSt}>
<Image />
</Provider>
</div>
);
};
export default Home;

Why do we need to create a base element class while working on UI in reactjs?

import $ from 'jquery';
export class BaseElement {
constructor(){
this.element = null; //jquery Object
}
appendToElement(el) {
this.createElement();
el.append(this.element);
}
createElement(){
let s = getElementString();
this.element = $(s);
}
getElementString(){
throw 'please override getElementString() in BaseElements';
}
}
why do we need to create this base element class for the UI??
You don't need to create any base component for UI. Below is the sample code for creating components.
Class component
import React from 'react';
export class LoginComponent from React.Component {
render() {
return (
//Your UI code comes here
)
}
}
Function Component
import React from 'react';
export default StaticComponent = (props) {
return (
//Your UI code here
)
}
Please refer React Site for more details.

React Native :element type is invalid expected a string but got object

I got this error and don't know where the problem is...
I am trying to import the header.js into the index.js
My index.js is:
//for ios application PLACE CODE HERE::::
//Import a library to help create a component
import React from 'react';
import { Text , AppRegistry } from 'react-native';
import Header from './src/components/header';
//Create a component
const App = () => {
return (
<Header/>
);
};
//Render it to the device
AppRegistry.registerComponent('yoyo', () => App);
And header.js:
import React from 'react';
import { Text } from 'react-native';
const Header = () => {
return <Text>Albums!</Text>;
};
export default Header;
Could anyone help me whats my wrong?
Change your header.js by following.
import React from 'react';
import { Text } from 'react-native';
class Header extends React.Component {
render() {
return <Text>Albums!</Text>;
}
}
export default Header;

Resources