Props undefined when calling a method in a react component - reactjs

I have a component with a list of child components. When I call a method from the parent component from the child component and, in the code I am using props, I get an
"Uncaught TypeError: Cannot read property 'activeStep' of undefined"
Where activeStep is a member of props.
Here is the relevant part om my code.
Parent component
import { Component, ReactNode, createElement, ReactFragment } from "react";
import { SlidingWizardStepComponent } from "./SlidingWizardStepComponent";
export interface SlidingWizardComponentProps {
activeStep: number;
}
export class SlidingWizardComponent extends Component<SlidingWizardComponentProps> {
getWizard = (): JSX.Element[] => {
if (!this.props.activeStep && this.props.activeStep !== 0) {
return [];
}
return = this.createWizard();
};
private setActiveStep(clickedStep: number): void {
if (clickedStep !== this.props.activeStep) {
this.props.setActiveStep(clickedStep);
}
}
private createWizard(): JSX.Element[] {
const wizardSteps: JSX.Element[] = [];
for (let i = 0; i < this.props.numberOfSteps; i++) {
wizardSteps.push(
<SlidingWizardStepComponent setActiveStep={this.setActiveStep} />
);
}
return wizardSteps;
}
render(): ReactNode {
return (
<div className="slide-wizard">
{this.getWizard()}
</div>
);
}
}
Child component
import { Component, ReactNode, createElement, ReactFragment } from "react";
export interface SlidingWizardStepComponentProps {
setActiveStep: (clickedStep: number) => void;
}
export class SlidingWizardStepComponent extends Component<SlidingWizardStepComponentProps> {
private setActiveStep(): void {
this.props.setActiveStep(this.props.stepIndex);
}
render(): ReactNode {
return (
<div onClick={() => this.setActiveStep()}>
Content
</div>
);
}
}
EDIT: When I add a constructor to the code and I log my props in the constructor, they seem to be set!
EDIT 2: The parent component itself is called by another component that looks like this in its stripped down form:
import { Component, ReactNode, createElement, ReactFragment } from "react";
import { SlidingWizardComponent } from "./components/SlidingWizardComponent";
import Big from "big.js";
import { SlidingWizardContainerProps } from "../typings/SlidingWizardProps";
import "./ui/SlidingWizard.css";
export interface State {
activeStep: number;
clickedStep: number;
}
export default class SlidingWizard extends Component<SlidingWizardContainerProps, State> {
state: State = {
activeStep: parseInt(this.props.activeStep.displayValue, 10),
clickedStep: parseInt(this.props.clickedStep.displayValue, 10)
};
resetActiveStep = (): void => {
this.props.activeStep.setValue(new Big(0));
this.setState({
activeStep: 0
});
};
setActiveStep = (clickedStep: number): void => {
this.props.clickedStep.setValue(new Big(clickedStep));
if (this.props.onActiveStepChange) {
this.props.onActiveStepChange.execute();
}
};
render(): ReactNode {
return (
<SlidingWizardComponent
activeStep={this.state.activeStep}
/>
);
}
}
This widget is used in a Mendix application. The parameters used in this top component are defined through an xml file.

Although I still do not know why the original code did not work, I was able to solve it by:
In the parent component in the createWizard method, change setActiveStep={this.setActiveStep} to setActiveStep={this.props.setActiveStep}
Remove the setActiveStep method from the parent component

Related

TypeError: Cannot read property 'vote' of null in render view

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(...)}
);
}

React: how to properly type the HOC component?

I searched around the suggestions but could not find any answer.
I'm basically think that I can properly type the HOC as follows:
This is my component at the moment:
// #flow
import React, { Component } from 'react';
import moment from 'moment';
import type { ApolloClient } from 'apollo-client';
import { convertDatesToISO } from 'components/Calendar/utils';
type Props = {
client: ApolloClient<any>,
location: {
search: string,
},
};
type SelectedDates = {
startOn: moment,
endOn: moment,
};
const withInitialSelectedDates = (WrappedComponent: Component<Props>): Component => {
return class extends Component<Props> {
initialSelectedDates: ?SelectedDates;
initialSelectedDatesFromQueryString(): ?SelectedDates {
const searchString = this.props.location.search;
const searchParams = new URLSearchParams(searchString);
const startOn = moment.utc(searchParams.get('start_date'));
const endOn = moment.utc(searchParams.get('end_date'));
if (!startOn.isValid() || !endOn.isValid()) return null;
if (startOn < moment.utc().startOf('day')) return null;
if (endOn < startOn) return null;
return { startOn, endOn };
}
setInitialSelectedDatesOnGraphQLClient(): void {
if (this.initialSelectedDates == null) return;
this.props.client.writeData({
data: {
selectedDates: convertDatesToISO([this.initialSelectedDates]),
},
});
}
componentDidMount(): void {
this.initialSelectedDates = this.initialSelectedDatesFromQueryString();
this.setInitialSelectedDatesOnGraphQLClient();
}
render(): React.Element {
return (
<WrappedComponent
initialSelectedDates={this.initialSelectedDates}
{...this.props}
/>
);
}
};
};
export default withInitialSelectedDates;
I think I can change:
const withInitialSelectedDates = (WrappedComponent: Component<Props>): Component => {
to this:
const withInitialSelectedDates = <PassedProps: {} & Props>(WrappedComponent: ComponentType<PassedProps>): ComponentType<PassedProps> => {
It will require importing ComponentType. My question is where should I change my current code and add PassedProps?
You'll want to follow the example from the "Injecting Props" section of the HOC Flow documentation. An example implementation could look like,
import * as React from 'react';
// Since Try Flow doesn't have access to these types
type ApolloClient<T> = any;
type moment = any;
type Props = {
client: ApolloClient<any>,
location: {
search: string,
},
};
type SelectedDates = {
startOn: moment,
endOn: moment,
};
function withInitialSelectedDates(
Component: React.AbstractComponent<Props>
): React.AbstractComponent<$Diff<Props, { initialSelectedDates: SelectedDates | void }>> {
return class WrappedComponent extends React.Component<
$Diff<Props, { initialSelectedDates: SelectedDates | void }>,
{ initialSelectedDates: SelectedDates | null }
> {
state = {
initialSelectedDates: null,
}
getInitialSelectedDatesFromQueryString(): SelectedDates | null {
if (true) {
return { startOn: 'start', endOn: 'end' };
} else {
return null;
}
// use actual implementation; I just needed to satisfy type check
}
setInitialSelectedDatesOnGraphQLClient(selectedDates: SelectedDates | null): void {
// implementation
}
componentDidMount(): void {
const initialSelectedDates = this.getInitialSelectedDatesFromQueryString();
this.setState({ initialSelectedDates });
this.setInitialSelectedDatesOnGraphQLClient(initialSelectedDates);
}
render() {
return (
<Component
{...this.props}
initialSelectedDates={this.state.initialSelectedDates}
/>
);
}
}
}
Try Flow

redux state changed but connected component didn't update, can't understand mutation

I know that problem is that there is a mutation. Because mostly there is no rerendering because of it. But, can't understand what's wrong in the way I'm doing this.
For data which I get from backend everything is fine, but if I try to change state from FE it's not working.
The problem is with groupDevicesBySelectedFilter(devicesGroups).
After action is done, I get response that state was changed in console, but as in the title no changings on FE.
Filter.tsx
import * as React from 'react'
import {IAppState} from '../../reducers'
import {connect} from 'react-redux'
import { Dropdown, Header, Icon } from 'semantic-ui-react'
import { INodeTreeFilters, INodeTreeDevicesInfo } from './type-definition';
import * as nodeTreeActions from '../../actions/node-tree';
import * as _ from 'lodash';
interface INodeTreeFilterProps{
filters: INodeTreeFilters;
selectGroupsFilter: any;
groupDevicesBySelectedFilter: typeof nodeTreeActions.groupDevicesBySelectedFilter;
devices: INodeTreeDevicesInfo
}
class NodeTreeFilter extends React.Component<INodeTreeFilterProps>{
public render() {
const {filters, selectGroupsFilter, groupDevicesBySelectedFilter, devices} = this.props;
const groupsFilterSelected = (event: React.SyntheticEvent<HTMLDivElement>, data: any) => {
selectGroupsFilter({id:data.value});
const devicesGroups=_.chain(devices).groupBy(data.value).map((v, i) => {
return {
id: i,
name: i,
devices: v
}
}).value();
groupDevicesBySelectedFilter(devicesGroups);
}
return (
<Header as='h4'>
<Icon name='filter' />
<Header.Content>
Group nodes by {' '}
<Dropdown
inline = {true}
options={filters}
onChange={groupsFilterSelected}
/>
</Header.Content>
</Header>
)
}
}
const mapStateToProps = (state: IAppState) => (
{
filters: state.sidebar.filters,
devices: state.sidebar.devices,
});
const mapDispatchToProps = {
selectGroupsFilter: nodeTreeActions.selectNodeTreeGroupFilter,
groupDevicesBySelectedFilter: nodeTreeActions.groupDevicesBySelectedFilter
};
export default connect(mapStateToProps, mapDispatchToProps)(NodeTreeFilter)
My reducer
export const devicesGroupsReducer = (state: IDevicesGroups = [], action: IActionWithPayload) => {
switch (action.type) {
case nodeTreeActions.GROUP_DEVICES_BY_SELECTED_FILTER:
return action.payload
default:
return state;
} };
export interface IActionWithPayload extends Action {
payload: any;
}
And finally my child component, which should rerendering.
import * as React from 'react'
import {List} from 'semantic-ui-react'
import {IAppState,} from '../../reducers'
import {connect} from 'react-redux'
import {INodeTreeDevicesInfo, INodeTreeDeviceInterfaces, IDevicesGroups} from './type-definition'
import * as nodeTreeActions from '../../actions/node-tree'
// import * as nodeTreeService from '../../services/node-tree'
import {requestError} from "../../actions/error";
interface INodeTreeProps{
devices: INodeTreeDevicesInfo ;
interfaces: INodeTreeDeviceInterfaces;
getDeviceInterfaces: typeof nodeTreeActions.getNodeTreeDeviceInterfaces;
requestError: typeof requestError;
deviceGroups: IDevicesGroups;
}
class NodeTree extends React.Component<INodeTreeProps> {
public generateParentTree = (array: any) => {
const tree = array.map((item:any) => (
<List.Item key={item.id}>
<List.Icon name={ "caret right"} />
<List.Content onClick={this.generateChildren} verticalAlign='middle'>
<List.Description>{item.name}</List.Description>
</List.Content>
</List.Item>
))
return tree
}
public generateChildren = () => {
console.log('I will generate children')
}
public render() {
const {devices, deviceGroups} = this.props;
const parentArray = deviceGroups !== undefined && deviceGroups.length !== 0 ? deviceGroups : devices;
const Tree = this.generateParentTree(parentArray)
console.log('')
return (
<div>
<List>
{Tree}
</List>
</div>
);
}
}
const mapStateToProps = (state: IAppState) => (
{
devices: state.sidebar.devices,
interfaces: state.sidebar.interfaces,
deviceGroups: state.sidebar.deviceGroups
});
const mapDispatchToProps = {
requestError,
getDeviceInterfaces: nodeTreeActions.getNodeTreeDeviceInterfaces
};
export default connect(mapStateToProps, mapDispatchToProps)(NodeTree)
Pls, never mind on public and private states in code
You are mutating your state in the reducer. You need to return a new state object and update it with your payload.
return {
...state,
IDevicesGroups: [...state.IDevicesGroups, action.payload]
}
Should be something like that.

trying to pass my arrays (props) into my publish function as selector

import { Mongo } from 'meteor/mongo';
import { Meteor } from 'meteor/meteor';
import React, {Component} from 'react';
import {check} from 'meteor/check';
export const Adressen = new Mongo.Collection('Phonebook');
if (Meteor.isServer) {
Meteor.publish('ArrayToExport', function(branches) {
check(branches, [Match.Any]);
if(branches.length > 10){
return this.ready()
};
return Adressen.find(
{branche: {$in: branches}}, {fields: {firmenname:1, plz:1}}
);
});
}
.
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import {Adressen} from "../api/MongoDB";
class ExportArray extends Component{
constructor(props){
super(props);
this.state = {
branches: this.props.filteredBranches
};
}
render(){
return(
<div>
<button onClick={this.exportArrays}></button>+
</div>
);
}
}
export default withTracker( (branches) => {
Meteor.subscribe('ArrayToExport', branches);
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
this.props.filteredBranche is a pure array,generated through controlled input field. this.props.filteredBranches changes as Input changes, in parent Component.
I thought I was sending my this.props.filteredBranches as an argument through withTracker function. But nothing is passed to the publish function.
if (Meteor.isServer) {
arrayExfct = function (array){
return {
find: {branche:{$in: array }},
fields: {firmenname:1, plz:1}
};
}
Meteor.publish('ArrayToExport', function (array) {
return Adressen.find(
arrayExfct(array).find, arrayExfct(array).fields);
});
}
.
export default withTracker( () => {
arrayExfct = function(array) {
return {
find: {branche: {$in: array}},
fields: {firmenname:1, plz:1}
}
}
var array = ['10555'];
Meteor.subscribe('ArrayToExport', array );
var arrayExfct = Adressen.find(arrayExfct(array).find, arrayExfct(array).fields);
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
It would help if you also added an example of where you used this component and how you pass props to it, but I think I see your problem.
You expect the local state in your rendering component to get into the withTracker container, but that would be the other way around. When you make the withTracker container, you are really making another react component that renders your display component (ExportArray) and passes the data (ArrayToExport) down into it.
So, props go like this currently:
external render -> withTracker component -> ExportArray
What you need to do it to get the filteredBranches (which you pass from a parent component?) from the props argument in withTracker and pass that to the subscribtion,
class ExportArray extends Component{
exportArrays () {
const { ArrayToExport } = this.props;
}
render(){
const { ArrayToExport } = this.props;
return(
<div>
<button onClick={this.exportArrays}></button>+
</div>
);
}
}
export default withTracker(propsFromParent => {
const { filteredBranches } = propsFromParent;
Meteor.subscribe('ArrayToExport', filteredBranches);
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
Hi the issue is with the code below. The parameter called branches is the props so branches.branches is the array you passed in.
export default withTracker( (branches) => {
Meteor.subscribe('ArrayToExport', branches);
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
Try the following.
export default withTracker( ({branches}) => {
Meteor.subscribe('ArrayToExport', branches);
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
Notice all that changed was
(branches)
became
({branches})
I solved my problem with a combination of Session Variables and State.
//Client
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import {Adressen} from "../api/MongoDB";
import {Meteor} from 'meteor/meteor';
import { Session } from 'meteor/session';
class ExportArray extends Component{
constructor(){
super();
this.state = {
x: [],
y: []
};
this.exportArrays = this.exportArrays.bind(this);
}
exportArrays(e){
e.preventDefault();
this.setState({x: this.props.filteredBranches});
this.setState({y: this.props.filteredPostleitzahlen});
}
render(){
var selector = {branche: {$in: this.state.x},plz: {$in: this.state.y}};
Session.set('selector', selector);
return(
<div>
<button onClick={this.exportArrays}> Commit </button>
</div>
);
}
}
export default withTracker( () => {
const ArrayfürExport = Meteor.subscribe('ArrayToExport', Session.get('selector') );
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
//Server
Meteor.publish('ArrayToExport', function (selector) {
console.log('von mongodb', selector);
return Adressen.find(
selector
, {
fields: {firmenname:1, plz:1}
});
});
}

react-test-renderer How to Create a Component With Context?

The Api for react-test-render says:
TestRenderer.create(element, options);
What are valid options for create?
Can I use it to set a component's context?
https://reactjs.org/docs/test-renderer.html#reference
The TestRenderer API doesn't support setting context directly - check out the create implementation here.
You can create a simple wrapper that just passes context:
import React from 'react'
import TestRenderer from 'react-test-renderer'
import PropTypes from 'prop-types'
// The example component under test
export default class WithContext extends React.Component {
static contextTypes = {
someProperty: PropTypes.any,
}
render () {
return (
<div>{ this.context.someProperty }</div>
)
}
}
describe('<WithContext>', () => {
it('renders the supplied context', () => {
const tree = TestRenderer.create(
<PassContext value={{ someProperty: 'context' }}>
<WithContext />
</PassContext>
)
tree.root.find(findInChildren(node =>
typeof node === 'string' &&
node.toLowerCase() === "context"
))
});
});
class PassContext extends React.Component {
static childContextTypes = {
someProperty: PropTypes.any,
}
getChildContext () {
return this.props.value
}
render () {
return this.props.children
}
}
// Returns a TestInstance#find() predicate that passes
// all test instance children (including text nodes) through
// the supplied predicate, and returns true if one of the
// children passes the predicate.
function findInChildren (predicate) {
return testInstance => {
const children = testInstance.children
return Array.isArray(children)
? children.some(predicate)
: predicate(children)
}
}

Resources