Is passing the _setTitle method with the props the way to go? - reactjs

want to update the title present in the Header
everytime we navigate to a new page.
Is passing the _setTitle method with the props the way to go?
import React, { Component } from 'react';
import PropTypes from 'prop-types';
Main App component
export class App extends Component {
constructor() {
super();
this.state = { pageTitle: 'My app' };
this._setTitle = this._setTitle.bind(this);
}
_setTitle(title) {
this.setState({ pageTitle: title });
}
render() {
const { pageTitle } = this.state.pageTitle;
return (
<div>
<Header title={pageTitle} />
{React.cloneElement(children, { setTitle: this._setTitle })}
<Footer />
</div>
);
}
}
Header and Footer Components
export class Header extends Component {
static propTypes = {
title: PropTypes.string
};
// ...
render() {
const { title } = this.props;
return <h2>{title}</h2>;
}
}
export class Footer extends Component {
// Footer code
}
Following are the different page Components:
export class Profile extends Component {
static propTypes = {
setTitle: PropTypes.func.isRequired
};
componentWillMount() {
this.props.setTitle('Profile');
}
}
export class Projects extends Component {
static propTypes = {
setTitle: PropTypes.func.isRequired
};
componentWillMount() {
this.props.setTitle('Projects');
}
// ...
}
export class ProjectForm extends Component {
static propTypes = {
setTitle: PropTypes.func.isRequired
};
componentWillMount() {
this.props.setTitle('New Project');
}
// ...
}
export class Translators extends Component {
static propTypes = {
setTitle: PropTypes.func.isRequired
};
componentWillMount() {
this.props.setTitle('Translators');
}
// ...
}
// ...
How can I improve upon this. I'm new to react so pls suggest If you have any ideas, I'll implement it. Thank you.

You can make use of Context and pass the setTitle method as a Context value, then you can create a Component that has the logic of setting the Title, A simple implementation would look like
const TitleContext = React.createContext();
export class App extends Component {
constructor() {
super();
this.state = { pageTitle: 'My app' };
this._setTitle = this._setTitle.bind(this);
}
_setTitle(title) {
this.setState({ pageTitle: title });
}
render() {
const { pageTitle } = this.state.pageTitle;
return (
<TitleContext.Provider value={{setTitle: this._setTitle}}
<div>
<Header title={pageTitle} />
{this.props.children}
<Footer />
</div>
</TitleContext.Provider>
);
}
}
class TitleSetter extends React.Component {
static propTypes = {
title: PropTypes.string.isRequired
}
componentDidMount() {
this.context.setTitle(this.props.title)
}
}
TitleSetter.contextTypes = TitleContext;
Now in any component you can simply render the TitleSetter like
export class Profile extends Component {
render() {
return (
<div>
<TitleSetter title="Profile" />
{/* other context */}
</div>
)
}
}
Also while looking into context, please look at the this question on how to access context outside of render

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

how to pass state to component

I created MExample component in that component i have created this
export default class MExample extends Component {
_validate() {
if (validateDate(this.state.choseDate).status) {
if (validateList(this.state.list).status) {
var list = this.state.list;
var choseDate = this.state.choseDate;
console.log(list+choseDate)
this.setState({ visibleModal: null , list:[], choseDate:''})
} else {
alert("select list date")
}
} else {
alert("select monthly date ")
}
}
render() {
return (
// jsx
)}
export default class Mnavigate extends Component {
render() {
return (
<MExample list={this.state.list} choseDate = {this.state.choseDate}/>
// can i access value like this ?
)
}
How to use this.state.list and this.state.choseDate in other component in which i'm importing this component <MExample here i want list and choseDate value />
<MExample list={this.state.list} choseDate={this.state.choseDate} />
and inside MExample component
access through
this.props.list and this.props.choseDate
class MExample extends React.Component{
render(){
console.log(this.props.list);
return null;
}
}
You can create properties and pass them as props.
Create a component as below
import React, { Component } from 'react'
class MExample extends Component {
// You can access them via this.props
validate = () => {
console.log(this.prop.list);
console.log(this.prop.choseDate);
}
render() {
let {list,choseDate} = this.props;
// your code comes here
return (
<div>
</div>
)
}
}
export default MExample;
Pass the state in the properties.
<MExample list={this.state.list} choseDate={this.state.choseDate} />

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

Unable to find an error

i know it's a minor and maybe a fool question but i'm stuck for about an hour at an error which i cant see. This is my code:
const ModalRoot = ({ modalType, modalProps, locale }) => {
if (!modalType) {
return <span />;
}
return (
<IntlProvider
locale={locale}
key={locale}
messages={messagesFor(locale)}
>
<div className="backdrop">
{renderAppropriateModal(modalType, modalProps)}
</div>
</IntlProvider>
);
};
The console shows an error in the if saying unexpected token. Why is this happening??
This can be your error...
check this samples.
Wrong place to declare.
import React from 'react';
class YOURCLASS extends React.Component {
constructor(props) {
super(props);
}
//do not place this ModalRoot here
const ModalRoot = ({ modalType, modalProps, locale }) => {
//contents
}
render(){
return(
<div>{yourContent}</div>
);
}
}
Right place to declare
import React from 'react';
//Place it here outside the class YOURCLASS
const ModalRoot = ({ modalType, modalProps, locale }) => {
//contents
}
class YOURCLASS extends React.Component {
constructor(props) {
super(props);
}
render(){
return(
<div>{yourContent}</div>
);
}
}
if you still intends to do it inside class... better use function instead...
import React from 'react';
class YOURCLASS extends React.Component {
constructor(props) {
super(props);
}
//function type ModalRoot
ModalRoot(modalType, modalProps, locale){
//contents
return <IntlProvider />;
}
render(){
const {modalType, modalProps, locale} = this.props;
let yourContent = this.ModalRoot(modalType, modalProps, locale);
return(
<div>{yourContent}</div>
);
}
}
hope it helps...

How to set component default props on React component

I use the code below to set default props on a React component but it doesn't work. In the render() method, I can see the output "undefined props" was printed on the browser console. How can I define a default value for the component props?
export default class AddAddressComponent extends Component {
render() {
let {provinceList,cityList} = this.props
if(cityList === undefined || provinceList === undefined){
console.log('undefined props')
}
...
}
AddAddressComponent.contextTypes = {
router: React.PropTypes.object.isRequired
}
AddAddressComponent.defaultProps = {
cityList: [],
provinceList: [],
}
AddAddressComponent.propTypes = {
userInfo: React.PropTypes.object,
cityList: PropTypes.array.isRequired,
provinceList: PropTypes.array.isRequired,
}
You forgot to close the Class bracket.
class AddAddressComponent extends React.Component {
render() {
let {provinceList,cityList} = this.props
if(cityList === undefined || provinceList === undefined){
console.log('undefined props')
} else {
console.log('defined props')
}
return (
<div>rendered</div>
)
}
}
AddAddressComponent.contextTypes = {
router: React.PropTypes.object.isRequired
};
AddAddressComponent.defaultProps = {
cityList: [],
provinceList: [],
};
AddAddressComponent.propTypes = {
userInfo: React.PropTypes.object,
cityList: React.PropTypes.array.isRequired,
provinceList: React.PropTypes.array.isRequired,
}
ReactDOM.render(
<AddAddressComponent />,
document.getElementById('app')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app" />
For those using something like babel stage-2 or transform-class-properties:
import React, { PropTypes, Component } from 'react';
export default class ExampleComponent extends Component {
static contextTypes = {
// some context types
};
static propTypes = {
prop1: PropTypes.object
};
static defaultProps = {
prop1: { foobar: 'foobar' }
};
...
}
Update
As of React v15.5, PropTypes was moved out of the main React Package (link):
import PropTypes from 'prop-types';
Edit
As pointed out by #johndodo, static class properties are actually not a part of the ES7 spec, but rather are currently only supported by babel. Updated to reflect that.
First you need to separate your class from the further extensions ex you cannot extend AddAddressComponent.defaultProps within the class instead move it outside.
I will also recommend you to read about the Constructor and React's lifecycle: see Component Specs and Lifecycle
Here is what you want:
import PropTypes from 'prop-types';
class AddAddressComponent extends React.Component {
render() {
let { provinceList, cityList } = this.props;
if(cityList === undefined || provinceList === undefined){
console.log('undefined props');
}
}
}
AddAddressComponent.contextTypes = {
router: PropTypes.object.isRequired
};
AddAddressComponent.defaultProps = {
cityList: [],
provinceList: [],
};
AddAddressComponent.propTypes = {
userInfo: PropTypes.object,
cityList: PropTypes.array.isRequired,
provinceList: PropTypes.array.isRequired,
}
export default AddAddressComponent;
If you're using a functional component, you can define defaults in the destructuring assignment, like so:
export default ({ children, id="menu", side="left", image={menu} }) => {
...
};
You can also use Destructuring assignment.
class AddAddressComponent extends React.Component {
render() {
const {
province="insertDefaultValueHere1",
city="insertDefaultValueHere2"
} = this.props
return (
<div>{province}</div>
<div>{city}</div>
)
}
}
I like this approach as you don't need to write much code.
use a static defaultProps like:
export default class AddAddressComponent extends Component {
static defaultProps = {
provinceList: [],
cityList: []
}
render() {
let {provinceList,cityList} = this.props
if(cityList === undefined || provinceList === undefined){
console.log('undefined props')
}
...
}
AddAddressComponent.contextTypes = {
router: React.PropTypes.object.isRequired
}
AddAddressComponent.defaultProps = {
cityList: [],
provinceList: [],
}
AddAddressComponent.propTypes = {
userInfo: React.PropTypes.object,
cityList: PropTypes.array.isRequired,
provinceList: PropTypes.array.isRequired,
}
Taken from:
https://github.com/facebook/react-native/issues/1772
If you wish to check the types, see how to use PropTypes in treyhakanson's or Ilan Hasanov's answer, or review the many answers in the above link.
You can set the default props using the class name as shown below.
class Greeting extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
// Specifies the default values for props:
Greeting.defaultProps = {
name: 'Stranger'
};
You can use the React's recommended way from this link for more info
For a function type prop you can use the following code:
AddAddressComponent.defaultProps = {
callBackHandler: () => {}
};
AddAddressComponent.propTypes = {
callBackHandler: PropTypes.func,
};
class Example extends React.Component {
render() {
return <h1>{this.props.text}</h1>;
}
}
Example.defaultProps = { text: 'yo' };

Resources