I'm having an issue where a component isn't rerendering. There's a base class that's needed to be overriden in order to perform testing.
I'm expecting it to fire after the handleChange function is called because someone typed in the textbox.
BASE CLASS
import * as React from 'react';
import { ISiteListRendererProps } from "./SiteListRendererProps";
import { SitelistState, SiteItem } from './ISitelistState';
export default class SiteListRendererBase extends React.Component<ISiteListRendererProps, SitelistState> {
public SiteItemsPremise : SiteItem[];
public SiteItemsOnline : SiteItem[];
public SearchText: string;
public constructor(props) {
super(props);
}
public render(): React.ReactElement<ISiteListRendererProps> {
return (<div id="SiteListRendererNull"></div>);
}
}
CHILD CLASS
import * as React from 'react';
import { ISiteListRendererProps } from "./SiteListRendererProps";
import SiteListRendererBase from './SiteListRendererBase';
export default class SiteListRenderer extends SiteListRendererBase {
public constructor(props) {
super(props);
this.state = {SearchText: "", SiteItemsPremise: null, SiteItemsOnline: null, OnPremiseUrl: null};
//this.handleChange = this.handleChange.bind(this);
}
public render(): React.ReactElement<ISiteListRendererProps> {
console.log("rerender: " + this.state.SearchText);
console.log(this.SearchText);
let siteItems = this.SiteItemsPremise.concat(this.SiteItemsOnline);
siteItems.sort((a, b) => (a.Title > b.Title ? 1 : -1));
return (
<div>
<div>
<input type="text" onChange={this.handleChange} />
</div>
{siteItems
.filter(
(item, index) =>
this.state.SearchText.length == 0 ||
item.Title.toUpperCase().indexOf(
this.state.SearchText.toUpperCase()
) >= 0
)
.map((item, index) => (
<p key={item.Url}>
{item.Title + " (" + item.Location + ")"}{this.SearchText}
</p>
))}
</div>
);
}
handleChange = (e) => {
this.setState({ SearchText: e.target.value});
this.forceUpdate();
}
}
PROPS AND STATE FOR COMPLETENESS
export class SitelistState {
public SiteItemsOnline: SiteItem[];
public SiteItemsPremise: SiteItem[];
public SearchText: string;
public OnPremiseUrl: string;
}
export class SiteItem {
public Title: string;
public Url: string;
public Location: string;
public WebTemplate: string;
}
export interface ISiteListRendererProps {
SiteItemsPremise : SiteItem[];
SiteItemsOnline : SiteItem[];
SearchText: string;
}
Related
In this component I get a Promise object in the properties, I try to put it in state, but when the view is rendered, I get the message "TypeError: Cannot read property 'vote' of null", asking for a solution to my problem, I spent two hours on it and I don't see the end. What should I do differently?
import { IVoteDetailsProps } from "./IVoteDetailsProps";
import { IVoteDetailsState } from "./IVoteDetailsState";
export class VoteDetails extends React.Component<IVoteDetailsProps, IVoteDetailsState>{
constructor(props: IVoteDetailsProps) {
super();
console.log(props)
}
componentDidMount() {
let data = this.props.voteDetails;
data.then(result => this.setState({
vote: result
}));
};
public render(): React.ReactElement<IVoteDetailsState> {
return (
<table >
<tbody>
{this.state.vote && this.state.vote.map(el => {
<tr id={el.id.toString()}>
<td>{el.title}</td>
<td>{el.voteType}</td>
</tr>
})}
</tbody>
</table>
)
}
}
export interface IVoteDetailsProps {
voteDetails: Promise<IVoteDetailsData[]>;
}
export interface IVoteDetailsData{
id: number;
title: string;
voteType: string;
}
import React = require("react");
import { VoteDetails } from "../VoteDetails/VoteDetails";
import { IVoteListProps } from "./IVoteListProps";
export class VoteList extends React.Component<IVoteListProps, {}> {
constructor(props: IVoteListProps) {
super(props);
console.log(props)
}
public render(): React.ReactElement<IVoteListProps> {
// const { vote } = this.state;
return (
<VoteDetails voteDetails={this.props.adminServicePanel.getVotesInfo()} />
)
};
}
public render(): React.ReactElement<IVoteSecurityAppProps> {
return (
<main className="ui main text container">
<VoteList adminServicePanel={this.props.adminPanelService}/>
</main>
);
import {HttpClient} from '#microsoft/sp-http';
import { reject } from 'lodash';
import {IAdminPanelService} from './IAdminPanelService';
import {IReportData} from './IReportData'
import { IVoteDetailsData } from './IVoteDetailsData';
import {IVoteInfo} from './IVoteInfo'
import {VoteOptions} from './VoteOptions';
export class AdminPanelService implements IAdminPanelService {
//////////////////////////////MOCK////////////////////////////////////////////
private voteInfos : IVoteDetailsData[];
private reportData : IReportData[];
//////////////////////////////MOCK////////////////////////////////////////////
constructor(private httpClient: HttpClient, private serverRelativeSiteUrl: string) {
//MOCK
this.voteInfos = [
{
id : 1,
title : "xxx",
voteType : "xx"
},
{
id : 2,
title : "xxx",
voteType : "xxx"
}
];
}
public getVotesInfo () : Promise<IVoteDetailsData[]> {
return new Promise<IVoteDetailsData[]>((resolve : (voteMiniInfo : IVoteDetailsData[]) => void, reject : (error: any) => void): void =>{
resolve(this.voteInfos);
})
}
}
export interface IAdminPanelService {
getVotesInfo:() => Promise<IVoteDetailsData[]>;
}
import * as React from 'react';
import styles from './VoteSecurityApp.module.scss';
import { IVoteSecurityAppProps } from './IVoteSecurityAppProps';
import { escape } from '#microsoft/sp-lodash-subset';
import { VoteList } from './VoteList/VoteList';
export default class VoteSecurityApp extends React.Component<IVoteSecurityAppProps, {}> {
public render(): React.ReactElement<IVoteSecurityAppProps> {
return (
<main className="ui main text container">
<VoteList adminServicePanel={this.props.adminPanelService}/>
</main>
);
}
}
export class VoteDetails extends React.Component<IVoteDetailsProps, IVoteDetailsState>
{
state = {
vote: null,
}
// change this
componentDidMount() {
this.props.voteDetails().then(result => this.setState({
vote: result
}));
};
// rest of your codes here
}
export class VoteList extends React.Component<IVoteListProps, {}> {
constructor(props: IVoteListProps) {
super(props);
console.log(props)
}
public render(): React.ReactElement<IVoteListProps> {
// const { vote } = this.state;
return (
<VoteDetails voteDetails=
{this.props.adminServicePanel.getVotesInfo} /> // change this line
)
};
}
All errors "TypeError: Cannot read property '......' of null" in spfx components, when you call
this.state.{varname}
or
this.props.{varname}
solves one of:
Add binding 'this' in constructor to method where rise error
this.{methodname} = this.{methodname}.bind(this)
You miss initialize state in constructor (for React.Component<props,state>)
this.state = {};
You use value from props or state and miss check it for null
In question I see all of this things. For example, in this peice of code state will not be initialized, otherwise component has state
export class VoteDetails extends React.Component<IVoteDetailsProps, IVoteDetailsState>{
constructor(props: IVoteListProps) {
super(props);
console.log(props)
//this.state == null - true
}
}
Second problem is this code
<VoteDetails voteDetails={this.props.adminServicePanel.getVotesInfo()}
getVotesInfo- return promise, not data. This bad practice, use state to hold data, for example
constructor(props: ...){
super(props);
this.state{
data: null
};
this._getData = this._getData.bind(this);
}
componentDidMount(){
this._getData();
}
async _getData(){
if(this.props.adminServicePanel){
let data = await this.props.adminServicePanel.getVotesInfo();
this.setStae({data});
}
}
render():...{
const data = this.state.data;
return(
{data && data.map(...)}
);
}
I have react app based on react-redux-typescript-boilerplate I've created this file:
import * as React from 'react';
import { connect } from 'react-redux';
import { RootState } from 'app/reducers';
export namespace LasarTitle {
export interface Props {
title: string;
loading: boolean;
};
};
#connect(
(state: RootState): LasarTitle.Props => {
return { title: state.title, loading: state.loading };
},
() => { }
)
export class LasarTitle extends React.Component<LasarTitle.Props> {
static defaultProps: Partial<LasarTitle.Props> = {
title: ''
};
constructor(props: LasarTitle.Props, context?: any) {
super(props, context);
}
render() {
const title = this.props.loading ? 'loading...' : this.props.title;
return (
<div>
<input readOnly={true} value={title} />
</div>
);
}
}
which is exactly the same as containers/App/index.tsx it have namespace and connect on component with the same name.
What's wrong with my code? I've didn't notice any typos or errors. What's the difference between my code and boilerplate?
The error is pretty explicit. You just need to move the namespace declaration after the class.
import * as React from 'react';
import { connect } from 'react-redux';
import { RootState } from 'app/reducers';
#connect(
(state: RootState): LasarTitle.Props => {
return { title: state.title, loading: state.loading };
},
() => { }
)
export class LasarTitle extends React.Component<LasarTitle.Props> {
static defaultProps: Partial<LasarTitle.Props> = {
title: ''
};
constructor(props: LasarTitle.Props, context?: any) {
super(props, context);
}
render() {
const title = this.props.loading ? 'loading...' : this.props.title;
return (
<div>
<input readOnly={true} value={title} />
</div>
);
}
}
export namespace LasarTitle {
export interface Props {
title: string;
loading: boolean;
};
}
Since the namespace and the class have the same name, they will be merged. One of the things merging will do is that there will be only one object at runtime representing the combined class-namespace. For reason of implementation such a merger requires that the class object be created first and then namespace members be added to it.
Edit
If your namespace only contains types this rule will not be applied, if it contains code (such as function or variable declarations) the rule will apply. The boiler-plate works because it contains no code. While not obvious, your namespace does contain code, the ; at the end of the interface will be transpiled to empty JS statements, and thus the rule is enforced.
The better solution would be to remove the ;
import * as React from 'react';
import { connect } from 'react-redux';
import { RootState } from 'app/reducers';
export namespace LasarTitle {
export interface Props {
title: string;
loading: boolean;
}
};
#connect(
(state: RootState): LasarTitle.Props => {
return { title: state.title, loading: state.loading };
},
() => { }
)
export class LasarTitle extends React.Component<LasarTitle.Props> {
static defaultProps: Partial<LasarTitle.Props> = {
title: ''
};
constructor(props: LasarTitle.Props, context?: any) {
super(props, context);
}
render() {
const title = this.props.loading ? 'loading...' : this.props.title;
return (
<div>
<input readOnly={true} value={title} />
</div>
);
}
}
What is a point in react. We have props who's readOnly and i cant edit it and we have state also can't edit from out of class space ?!
I follow tutorials ...
if i setup {this.state.myStyle} , myStale become's readonly ?!
Here is whole class :
import * as React from "react";
import { CSSProperties } from "react";
import * as ReactDOM from "react-dom";
import { Label } from "../../components/label/label";
import IApp from "../../interfaces/global-interfaces";
import Services from "../../services/services";
import { HeaderI, HeaderStateI } from "./header-interface";
// import { myStyle } from "./style";
enum myEventList {
iNeedSomeUpdate = "i-need-some-update",
}
export class Header extends React.Component< HeaderI, HeaderStateI , any > {
public myEvent = Services.CreateEvent(myEventList.iNeedSomeUpdate, {self: this} );
public myRef: React.RefObject<HTMLDivElement>;
public myDOM: Element | Text;
private myStyle: IApp.MyMinimumCssInterface = {
display: "block",
background: "#559d96",
height: "100px",
textAlign: "center",
};
constructor(args: any) {
super(args);
this.state = { enabledComponent : true,
visibility: true,
debugView: false,
background: args.background,
elements: [],
// tslint:disable-next-line:object-literal-shorthand
myStyle: this.myStyle,
};
// e.detail.data.self..background = this.state.background;
this.myRef = React.createRef();
this.add = this.add.bind(this);
}
// Override func
public componentDidMount() {
this.myDOM = this.myRef.current;
this.myDOM.addEventListener(myEventList.iNeedSomeUpdate, this.updateOnMyEvent);
}
public updateOnMyEvent(e: CustomEvent) {
e.detail.data.self.printMe();
console.log("My custom event is done!");
e.detail.data.self.adapt();
}
public printMe() {
console.log("Layout Header is active and update is on");
}
public render() {
if ( this.state.debugView === false ) {
return (
<div ref={this.myRef} style={this.state.myStyle} onClick={this.TestEvent.bind(this)} >
<Label name="headerName" text="i am header paragraph!" />
{this.state.elements.map((i: any) => {
return <span key={i} >{i}</span>;
})}
</div>
);
} else {
this.printMe();
return (
<div style={this.state.myStyle} ref={this.myRef} >
<Label name="headerName" text="i am header paragraph!"/>
{this.state.elements.map((i: any) => {
return <li key={i} >{i}</li>;
})}
</div>
);
}
}
public componentDidUpdate(prevProps: any) {
// Typical usage (don't forget to compare props):
console.warn("prevProps name is: " + prevProps.name);
if (this.props.background !== prevProps.background) {
this.printMe();
} else {
console.log("Background is same no update.");
}
}
public add = (id: number, content: any, event: any ) => {
let localArr: any[] = [];
localArr = this.state.elements;
localArr.push(React.createElement("div", { key: id , onClick : null }, content));
this.setState(
{
elements: localArr,
visibility : false,
},
);
// tslint:disable-next-line:no-unused-expression
console.log(" add from class in state elements, visible is " , this.state.visibility );
}
public TestEvent(event: MouseEvent) {
this.add( 1 , "fffff", null);
this.add( 2 , "zzzzzz", null);
this.myDOM.dispatchEvent(this.myEvent);
}
public adapt() {
this.myStyle.background = "lime";
this.setState({
myStyle: this.myStyle,
});
}
}
Because myStyle is 'frozen', you need to clone the object, make changes and then write it back using setState.
In ES6 you can use a pattern like this:
public adapt() {
const {myStyle} = this.state
let newMyStyle = {...myStyle}
newMyStyle.background = "lime";
this.setState({
myStyle: newMyStyle,
});
}
There are several ways how to manage this i.e.
const myStyle = Object.assign({}, this.state.myStyle, { background: "lime" })
this.setState({ myStyle })
I've got 3 components as follows and the first on change on the input field makes it lose focus. I've tried giving the input fields and divs keys but it didn't solve my problem. I'm new to React so I might be doing something basic incorrectly. I'm not creating a new function inside a render method which is the reason for most losing focus issues documented in stackoverflow. Hence the reason for the new post.
Index.tsx
import * as React from "react";
import { IQuery, Queries } from "./components/Queries/Queries";
interface IManageResponseState {
primaryQuery: IQuery;
alternateQueries: IQuery[];
}
export class ManageResponse extends React.Component<any, IManageResponseState> {
constructor(props: any) {
super(props);
this.state = {
primaryQuery: {
queryText: "This is my primary query text",
id: 0
}, alternateQueries: [{
queryText: "this is my alternate query text 1",
id: 1
},
{
queryText: "this is my alternate query text 2",
id: 2
}]
};
this.addFunc = this.addFunc.bind(this);
this.removeFunc = this.removeFunc.bind(this);
this.primaryChangedFunc = this.primaryChangedFunc.bind(this);
this.alternateChangedFunc = this.alternateChangedFunc.bind(this);
}
public addFunc(text: string) {
const newQueries = this.state.alternateQueries.filter(q => true);
newQueries.push({ id: 0, queryText: text });
this.setState({ alternateQueries: newQueries });
};
public removeFunc(index: number) {
this.setState({ alternateQueries: this.state.alternateQueries.splice(index, 1) });
console.log("Remove called:" + index);
};
public primaryChangedFunc(text: string) {
const query = {
queryText: text,
id: 0
};
this.setState({ primaryQuery: query });
console.log("changed primary called:" + text);
}
public alternateChangedFunc(index: number, text: string) {
const item = this.state.alternateQueries[index];
const newQueries = this.state.alternateQueries.filter(q => true);
newQueries[index] = {
queryText: text,
id: item.id
};
this.setState({ alternateQueries: newQueries })
console.log("changed alternate called:" + text);
}
public render() {
return (
<React.Fragment>
<Queries primaryQuery={this.state.primaryQuery} alternateQueries={this.state.alternateQueries} onAddQuery={this.addFunc} onRemoveQuery={this.removeFunc} onPrimaryChanged={this.primaryChangedFunc} onAlternateQueryChanged={this.alternateChangedFunc} />
</React.Fragment>
);
}
}
Queries.tsx
import * as classnames from 'classnames';
import * as React from "react";
import { AlternateQueryItem } from "../AlternateQueryItem/AlternateQueryItem";
import './Queries.scss'
export interface IQuery {
queryText: string;
id: number;
}
export interface IQueryProps {
primaryQuery: IQuery;
alternateQueries: IQuery[];
onPrimaryChanged: (queryText: string) => void;
onAlternateQueryChanged: (index: number, queryText: string) => void;
onAddQuery: (queryText: string) => void;
onRemoveQuery: (atIndex: number) => void;
}
interface IQueryState {
primaryQueryInvalid: boolean;
}
class Queries extends React.Component<IQueryProps, IQueryState> {
constructor(props: IQueryProps) {
super(props);
this.onPrimaryQueryChanged = this.onPrimaryQueryChanged.bind(this);
this.state = { primaryQueryInvalid: false };
}
public render() {
return (
<main className="query-main-container">
<div className="primary-query-container">
<div className="form-group">
<h5 className={classnames("primary-query-header", { 'invalid': this.state.primaryQueryInvalid })}>Primary query</h5>
<div className="primary-query">
<div>{ "hello " + this.state.primaryQueryInvalid }</div>
<input type="text" value={this.props.primaryQuery.queryText} onChange={this.onPrimaryQueryChanged} className="form-control"/>
</div>
</div>
</div>
<div>
<h5 className="query-header">Query variations</h5>
<hr />
</div>
<div className="query-container">
{this.props.alternateQueries && this.props.alternateQueries.map((q, i) => this.renderQuery(q, i))}
</div>
</main>
);
}
public renderQuery(query: IQuery, key: number) {
return (
<AlternateQueryItem onChanged={this.props.onAlternateQueryChanged} query={query} key={key} index={key} />
);
}
private onPrimaryQueryChanged = (ev: React.FormEvent<HTMLInputElement>) => {
this.setState({ primaryQueryInvalid: ev.currentTarget.value === ""})
this.props.onPrimaryChanged(ev.currentTarget.value)
}
}
export { Queries };
AlternateQueryItem.tsx
import * as React from "react";
import { IQuery } from "../Queries/Queries";
export interface IAlternateQueryProps {
query: IQuery;
onChanged: (index: number, queryText: string) => void;
index: number;
}
class AlternateQueryItem extends React.Component<IAlternateQueryProps> {
constructor(props: IAlternateQueryProps) {
super(props);
this.onItemChanged = this.onItemChanged.bind(this);
}
public render() {
return (
<div className="form-group">
<div className="alternate-query">
<input type="text" value={this.props.query.queryText} onChange={this.onItemChanged} className="form-control" />
</div>
</div>
);
}
public onItemChanged = (ev: any) => {
this.props.onChanged(this.props.index, ev.currentTarget.value)
}
}
export { AlternateQueryItem };
module of MarketEvent.tsx,The function is a centralized controller:
import * as React from 'react';
import EventList from './EventList';
import FullReduce from './FullReduce';
import './MarketEvent.less'
export default class MarketEvent extends React.Component<{},any> {
public id: string;
public name: string;
public target: JSX.Element;
public defaultId: string;
public state = {
target: this.target,
defaultId: 'marketMain'
};
public constructor(defaultId:any) {
super(defaultId);
this.changeTarget = this.changeTarget.bind(this);
this.target = this.state.target;
console.log('传到父组件的ID:',this.state.defaultId);
switch (this.state.defaultId) {
case 'marketMain':
this.target = <EventList currentRoute={this.changeTarget}/>;
break;
case 'fullReduce':
this.target = <FullReduce currentRoute={this.changeTarget}/>;
break;
default:
this.target = <EventList currentRoute={this.changeTarget}/>;
}
}
public componentWillMount(){
console.log('componentWillMount MarketEvent');
}
public componentDidMount(){
console.log('componentDidMount MarketEvent');
}
public changeTarget = (id: string) => {
console.log('子组件传到父组件的ID:',this.state);
this.setState({
defaultId: id
})
};
public render(){
return(
<div>
{this.target}
</div>
)
}
}
module of EventList.tsx,The function is to show 3 lists.:
import * as React from 'react';
import './MarketEvent.less'
interface EventListProps {
currentRoute: any
}
export default class EventList extends React.Component<EventListProps,any> {
public componentWillMount(){
console.log('componentWillMount EventList')
}
public componentDidMount(){
console.log('componentDidMount EventList')
}
public refName = (id: string) => {
this.props.currentRoute(id);
};
public render(){
return(
<div className="market_event">
<div className="market_top">
营销活动
</div>
<div className="market_body">
<ul className="market_ul">
<li onClick={this.refName.bind(this,'fullReduce')}><a href="javascript:;"><span className="l1">减</span>
<div className="event_box">
<h2>店铺满减</h2>
<i>促销</i><i>客单价</i>
<p>下单满足条件可享受减免</p>
</div>
</a></li>
<li><a href="javascript:;"><span className="l2">店</span>
<div className="event_box">
<h2>店铺代金券</h2>
<i>拉新</i><i>引流</i>
<p>进店时可领取店铺专用代金券</p>
</div>
</a></li>
<li><a href="javascript:;"><span className="l3">促</span>
<div className="event_box">
<h2>折扣促销</h2>
<i>新品</i><i>爆款</i>
<p>下单满足条件可享受减免</p>
</div>
</a></li>
</ul>
</div>
</div>
)
}
}
module of FullReduce.tsx,Act as a detail page in a list:
import * as React from 'react';
import {Button} from "antd";
interface FullReduceProps {
currentRoute: any
}
export default class FullReduce extends React.Component<FullReduceProps,any> {
public componentWillMount(){
console.log('componentWillMount FullReduce');
}
public componentDidMount(){
console.log('componentDidMount FullReduce')
}
public refName = (id:string) => {
this.props.currentRoute(id);
};
public render(){
return(
<div>
<Button htmlType='button' onClick={this.refName.bind(this,'marketMain')}>返回</Button>
已经进入了店铺满减页面了
</div>
)
}
}
The effect I want to achieve is click on a list in EventList. tsx, return an ID to the centralized controller MarketEvent. tsx, and then render the corresponding page by judgment,But after clicking, I found that defaultID had changed, and the page was not rendered.I print this.state on the console and find that the target in this.state is undefined.
I don't know why. Is there a good hand to help me? Thank you very much!!
The MarketEvent constructor does not run again after the state is changed. If you want the switch statement to run again to choose a different subcomponent to show, move it to the render method:
export default class MarketEvent extends React.Component<{},any> {
public id: string;
public name: string;
public defaultId: string;
public state = {
defaultId: 'marketMain'
};
public constructor(defaultId:any) {
super(defaultId);
this.changeTarget = this.changeTarget.bind(this);
console.log('传到父组件的ID:',this.state.defaultId);
}
public componentWillMount(){
console.log('componentWillMount MarketEvent');
}
public componentDidMount(){
console.log('componentDidMount MarketEvent');
}
public changeTarget = (id: string) => {
console.log('子组件传到父组件的ID:',this.state);
this.setState({
defaultId: id
})
};
public render(){
let target;
switch (this.state.defaultId) {
case 'marketMain':
target = <EventList currentRoute={this.changeTarget}/>;
break;
case 'fullReduce':
target = <FullReduce currentRoute={this.changeTarget}/>;
break;
default:
target = <EventList currentRoute={this.changeTarget}/>;
}
return(
<div>
{target}
</div>
)
}
}