React - passing props to another component whenever component is updated - reactjs

I have 2 components, one is Grid component where I show list of some data. Another one is "functional" component which contains logic like sorting, paging, etc which will be in every Grid component. Grid component can contain some own specific logic also.
My question is how I can pass props to functional component whenever something was changed in Grid component. Right now it only pass props for the first time Grid component is initialized, after that functional component works with old props which are nevew actualized.
Grid component is connected to redux store but I dont think it does matter here.
I am not sure if initializing class component via state is good approach, is there another way how to do it?
Code to grid component
type ProcessListProps =
ProcessState.IProcessesStateType
& typeof ProcessState.actionCreators
& RouteComponentProps<{}>;
type State = {
sharedFunctionality: SharedGridFunctions;
}
class ProcessList extends React.Component<ProcessListProps, State> {
constructor(props) {
super(props);
this.state = {
sharedFunctionality: new SharedGridFunctions(this.props)
}
}
componentDidMount() {
this.props.requestData(this.props.dataQuery);
}
componentDidUpdate(oldProps): void {
if ((oldProps.dataQuery.context !== this.props.dataQuery.context)) {
this.props.requestData(this.props.dataQuery);
}
if (oldProps.sort !== this.props.sort) {
this.state.sharedFunctionality.buildSortExpression();
}
}
..render - removed for brevity
}
Code for Functional component
interface ISharedGridFunctions extends IGridBase, IBaseFilterActionCreator, IBaseActionCreator {
}
export default class SharedGridFunctions extends React.PureComponent<ISharedGridFunctions,{}>{
constructor(props) {
super(props);
}
componentDidUpdate() {
console.log("shared component updated");
}
pageChange = (event) => {
this.setPaging(event.page.skip, event.page.take);
};
setPaging = (skip: number, take: number) => {
this.props.setPaging(take, skip);
};
sortChange = (event) => {
const dataQuery = this.props.dataQuery;
this.props.setOrder(dataQuery, event.sort);
};
//build sort expression in dataQuery object which is sent to api calls
buildSortExpression = () => {
let expression = "";
this.props.sort.map((sort) =>
expression += `${sort.field} ${sort.dir}, `
);
expression = expression.substring(0, expression.length - 2);
const dataQuery = this.props.dataQuery;
if (expression.length > 0) {
dataQuery.sort.orderBy = expression;
}
else {
dataQuery.sort.orderBy = undefined;
}
this.props.setOrder(dataQuery, this.props.sort, true)
}
removeByKey = (myObj, deleteKey) => {
return Object.keys(myObj)
.filter(key => key !== deleteKey)
.reduce((result, current) => {
result[current] = myObj[current];
return result;
}, {});
}
render() {
return null;
}
}

Related

React - Class component doesn't receive any params

I have a simple class Component:
class SearchedUserWrapper extends Component {
constructor(props) {
super(props);
this.state = {
searchedPhrase: "",
pageNumber: 1
};
this.increment = this.increment.bind(this);
}
GetUserForSearchResult = (postAmount, pageNumber) => {
const list = [];
for (let index = 0; index < postAmount; index++) {
list.push(<SearchResultUser CurrentPage={pageNumber}></SearchResultUser>);
}
return list;
}
increment = () => {
this.setState({ pageNumber: this.state.pageNumber + 1 })
console.log(this.state.pageNumber + 0);
}
render() {
return (<div>
{this.GetUserForSearchResult(5, this.props.pageNumber)}
<Button onClick={this.increment}> Current page {this.state.pageNumber}</Button>
</div>);
}
}
and function GetUserForSearchResult receives a state from SearchUserWrapper class. My SearchResultUser looks like this:
class SearchResultUser extends Component {
render() {
{console.log(this.props.CurrentPage)}
return (
<div className="user-searchresult">
{this.props.CurrentPage}
</div>);
}
}
export default SearchResultUser;
And console log says that this props are undefined, and the div is empty.
My goal is to have the effect that everytime I click "Current page" button, to refresh all the SearchResultUser component so that it displays a state passed as parameter. What am I doing here wrong? Why does it says to be undefined?
EDIT:
I tried couple of things and discovered something.
If I send the state in the params directly, for example:
render() {
return (<div>
<SearchResultUser CurrentPage={this.state.pageNumber}></SearchResultUser>
</div>
It seems to work, but the order of sending the state to the function, which passes it to params of component doesn't work.
GetUserForSearchResult = (postAmount, pageNumber) => {
const list = [];
for (let index = 0; index < postAmount; index++) {
list.push(<SearchResultUser CurrentPage={pageNumber}></SearchResultUser>);
}
return list;
}
render() {
return (<div>
<SearchResultUser CurrentPage={this.state.pageNumber}></SearchResultUser>
</div>);
Can somebody explain why is it happening like this?
Edit:
In this place(below) I think you have to pass state instead of props, because the main component(SearchedUserWrapper) doesn't receive any props, so this is undefined.
{this.GetUserForSearchResult(5, this.props.pageNumber)}
https://codesandbox.io/s/stackoverflow-71594634-omvqpw
First message:
Did you check if the page number was updated?
If the next state depends on the current state, the doc of react recommend using the updater function form, instead:
this.setState((state) => {
return {quantity: state.quantity + 1};
});
You should call super(props) before any other statement. Otherwise, this.props will be undefined in the constructor at SearchResultUser

React parent is unmounted for child?

I have a React Parent and Child component. The Child has to be able to update the state of the Parent component. However, I have the issue that my Parent is unmounted when the Child wants to update the Parent, and I'm not sure why or how to fix that?
I'm pasting the simplified code that is relevant to the issue. In this case, the child wants to update the page Title that is rendered in the Parent. But sadly, in the componentDidMount() of the Child (where this update happens) the Parent is already unmounted.
The index.js loads the Child directly but it wraps the Parent component around it. I guess that has something to do with the Parent component unmounting?
Parent:
type Props = TranslationProps & {
Component: any
};
type State = {
setTitle: string
};
export class Layout extends Component<Props, State> {
_isMounted = false;
constructor(props:Props) {
super(props);
this.state = {
setTitle: ""
};
}
componentDidMount() {
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
const { Component, ...restProps } = this.props;
return (
<Component
setTitle={title=> this._isMounted && this.setState({ setTitle: title})}
isMounted={this._isMounted}
{...restProps}
/>
);
}
}
Child:
type Props = TranslationProps & {
setTitle: (data: string) => void,
isMounted: boolean
};
componentDidMount() {
const { t } = this.props;
if(this.props.isMounted) {
this.props.setTitle("Test title");
}
}
Index:
const unwrap = component => {
if(component.WrappedComponent) {
return unwrap(component.WrappedComponent);
}
return component;
}
let reactMounts = document.querySelectorAll("[data-react]");
for (let reactMount of reactMounts) {
let Component = Components[reactMount.dataset.react];
let UnwrappedComponent = unwrap(Component);
console.log("Rendering", Component, " (", UnwrappedComponent, ") to ", reactMount);
let data = {
...reactMount.dataset,
Component
};
delete data.react;
render(
React.createElement(Components['Layout'], data),
reactMount
);
}
Reason: Accoring to React life-cycle, Child Component rendered before ComponentDidMount (where _isMounted = true). So, I guess the this._isMounted variable in setTitle props always = false => bug.
Solution: I recommend that you should create callback function in class, after that passing in setTitle props.

Calling props from a container

I am a little confused on the idea of using props in the context I am using for my React app. In my component, I need to check if the value of a certain prop (props.companyCode) matches a certain string, and only then will it print out a <p> of what I need. Below is what I have for calling the prop in the component:
Components/CompanyContact.jsx
class CompanyContact extends React.Component {
help() {
if (this.props.companyInfoList.companyCode === '1234') {
return <p>something</p>;
}
return <p>somethingelse</p>;
}
render() {
const help = this.help();
return (
<div>
{help};
</div>
)}}
export default CompanyContact;
And this is what I have for the container:
Container/InfoContainer.jsx
class InfoContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
companyInfoList: null,
};
}
async componentWillMount() {
const companyInfoCachedData = CachingService.getData('companyInfoList');
if (companyInfoCachedData) {
this.setState({ companyInfoList: companyInfoCachedData });
return;
}
}
async getCompanyInfo(accessToken) {
try {
const companyProfileResponse = await requestAWSGet('api/company-profile', undefined, accessToken);
CachingService.setData('companyInfoList', companyProfileResponse);
this.setState({ companyInfoList: companyProfileResponse });
} catch (err) {
throw err;
}
}
render() {
return (
<CompanyContact companyInfoList={this.state.companyInfoList} />
);
}
}
export default InfoContainer;
Nothing is returned when I run the application and I believe it's because I'm not calling the prop correctly in my component but I am unsure as to how to go about fixing it. I'm fairly new to working with props so still trying to get my bearings.
I'm assuming you are getting an error somewhere because of this not having props and this.props.companyInfoList.companyCode trying to access a property on a non object. this.props.companyInfoList is initially set to null so accessing a property on it will break.
A few strategies to fix the problem:
Default it to an empty object
this.state = {
companyInfoList: {},
}
Block the rendering of the component until it has a value:
if (this.state.companyInfoList) {
return (
<CompanyContact companyInfoList={this.state.companyInfoList} />
);
} else {
return null;
}
Check that the prop is an object and has the key companyCode on it:
if (this.props.companyInfoList &&
this.props.companyInfoList.companyCode &&
this.props.companyInfoList.companyCode === '1234') {
In addition, this will be in the wrong context and the changes above will most likely no be enough. Try changing to an arrow function like this:
help = () => {
// your code here
}
I would personally refactor that component logic and directly use the prop value inside the render method like:
class CompanyContact extends React.Component {
render() {
const { companyInfoList } = this.props;
return companyInfoList && companyInfoList.companyCode === '1234' ? (
<p>something</p>
) : (
<p>somethingelse</p>
)
}
}
export default CompanyContact;

Is a bad practice to have an object with internal state as an instance property in a React component?

Is a bad practice to have an object with internal state as an instance property in a React component?
for example
class PageCacher {
constructor(fetchMethod) {
this.fetchMethod = fetchMethod
this.pages = []
}
async getPage(page) {
if (this.pages[page]) {
return this.pages[page]
} else {
const result = await this.fetchMethod(page)
this.pages[page] = result
return result
}
}
}
class ItemList extends React.Component {
constructor(props) {
super(props)
this.pageCacher = new PageCacher(props.fetchServiceMethod)
}
hanldeFetchPage = (page) => {
this.pageCacher.getPage(page).then(result => {
this.setState({items: result})
})
}
}
PageCache keeps the pages requested stored and returns the result if present if not makes the service call.
Since you are initializing pageCacher in the constructor, it will only receive the props present at time of the component mounting.
This means that if any of the props change, pageCacher will NOT receive those updated props.
Link to docs on component constructor

Adding onclick handler to existing react component

Consider this
class CarMaker extends React.Component {
constructor(props) { super(props); }
handleEvent() { /* common handler code */ }
getComponent() {
let component = null;
switch(type) {
case 'hatchback':
// unique logic about hatchbacks
// consider attribues computed inside each case
component = <HatchbackMaker {...hatchbackAttrs} />;
break;
case 'sedan':
component = <SedanMaker {...sedanAttrs} />;
break;
case 'truck':
component = <TruckMaker {...truckAttrs} />;
break;
}
// Is there any way to attach event handler like this dynamically?
component.onClick = this.handleEvent.bind(component);
return component;
}
render() {
let arr = [];
dataList.forEach(function(dataItem) {
arr.push(this.getComponent(dataItem.type));
}, this);
return (<div>{arr}</div>;)
}
}
What's the best way to add onClick handler to an existing component(held in a variable) and bind that component itself as 'this' value?
Why not assign the onClick event at the time of assignment
getComponent() {
let component = null;
switch(type) {
case 'hatchback':
// unique logic about hatchbacks
// consider attribues computed inside each case
component = <HatchbackMaker {...hatchbackAttrs} onClick={this.handleEvent.bind(this)}/>;
break;
case 'sedan':
component = <SedanMaker {...sedanAttrs} onClick={this.handleEvent.bind(this)}/>;
break;
case 'truck':
component = <TruckMaker {...truckAttrs} onClick={this.handleEvent.bind(this)}/>;
break;
}
return component;
}
I have shown the props in the above example separate from the attributes since I don't know what these contain, you can pass with the respective prop attributes as well.
You want to create the element dynamically instead based on the type that comes through.
Make sure you change the type to capital letter at start.
E.g. "sedan" --> "Sedan"
Or use JS to uppercase it.
class Cars extends React.Component {
constructor(props) {
super(props);
this.state = {
carData: []
};
}
OonClickHandler() {
/* common handler code */
}
componentWillMount() {
//Api call to server to get car types just for example
$.ajax("someUrl", success: (data) => {
//[ { title: 'Honda City', type: 'sedan', shapeSVGURL: '<url>', . . //more properties . }, { title: 'VW Golf', type: 'hatchback', otherUniqueAttrs: [] . //more properties . } ]
this.setState({carData: data});
});
}
render() {
return (
<div>
//Make sure that type has upper case later at start or use JS to make it so
this.state.carData.map(car => React.createElement(car[type], {...car, onClick={this.onClickHandler}}))
</div>
)
}
}
class Honda extends React.Component {
render() {
return (
)
}
}
class VWGolf extends React.Component {
render() {
return (
)
}
}

Resources