Adding onclick handler to existing react component - reactjs

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

Related

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;

How to initialize the state of each item component in the component array in React?

const products = [
{
id: 1,
...
},
{
id: 2,
...
},
{
id: 3,
...
},
];
I created the ProductList component, which contains 3 Product components:
class ProductList extends Component {
constructor(props) {
super(props)
}
render() {
const productComponents = products.map((product) => (
<Product
key = {'product-' + product.id}
id = {product.id}
...
/>
));
return (
<ul className="holder-list row">
{productComponents}
</ul>
);
}
}
class Product extends Component {
constructor(props) {
super(props)
}
render() {
return(..)
}
}
How and in which component in the constructor to set a different initial state for all three products?
I want set the initial value of this.state for each Product different.
Example:
for Product with id:1 - this.state={color: blue},
for Product with id:2 - this.state={color: yellow},
for Product with id:3 - this.state={color: red}.
How can I do something like this?
This is how you could approach setting state color for Product. These are both inspired from a great article You Probably Don't Need Derived State which provides some great examples on how to handle "derived state".
ProductList - Create a method the returns a string color value based on your id to color requirements. This can be outside of the class definition, it doesn't/shouldn't need to be a method on class ProductList as it doesn't need the this or similar. Add an additional prop, something like defaultColor, that is passed to each instance of Product:
const getColor = id => {
switch (id) {
case 1:
return 'blue';
case 2:
return 'yellow';
case 3:
return 'red'
default:
return '';
}
};
// ...
render() {
const productComponents = products.map((product) => (
<Product
key = {'product-' + product.id}
id = {product.id}
defaultColor={getColor(product.id)}
...
/>
));
}
Product - Set initial state using the defaultColor prop being passed in. Using a different property would allow each Product component to fully control it's own color state value/changes with something like an <input />, but copy over the initial color value:
class Product extends Component {
state = { color: this.props.defaultColor };
// ...
render() {
return ({/* ... */});
}
}
Here is a StackBlitz demonstrating the functionality in action.
The other options is using static getDerivedStateFromProps() in Product. It conditionally checks if the id prop has changed to avoid setting state unnecessarily and overriding Product local state values. We are keeping track of the previous id value so that it can be used in the conditional statement to see if any changes actually happened:
class Product extends Component {
constructor(props) {
super(props);
this.state = {
prevId: -1,
color: ''
};
}
static getDerivedStateFromProps(props, state) {
if (props.id !== state.prevId) {
switch (props.id) {
case 1:
return { color: 'blue', prevId: props.id };
case 2:
return { color: 'yellow', prevId: props.id };
case 3:
return { color: 'red', prevId: props.id };
default:
return null;
}
}
return null
}
render() {
return ({/* ... */});
}
}
Here is a StackBlitz demonstrating this functionality.
It's hard to say exactly how to approach this as it may be likely you do not need state in Product. That Product can act as a "dumb" component just receiving props and emitting value changes to a higher order component like ProductList.
Hopefully that helps!

React - passing props to another component whenever component is updated

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

ReactJs - Merge state from Parent to Son in a hereditariness structure

Im trying to use some Object Pattern in React Components because the usual Component->child structure require often a code rewriting.
class SuperComponentEveryOneWillLove extends React.component
{
constructor(props){
this.state = { master_state_all_will_use : 0 }
this.commonFunction = this.commonFunction.bind(this);
this.getMasterState = this.getMasterState.bind(this);
}
commonFunction() { return do_something; }
getMasterState() { return this.state.master_state_all_will_use }
}
class PoorSon extends SuperComponentEveryOneWillLove
{
constructor(props){
this.state = { for_me_only : 0 }
}
render() {
<span>
{ this.state.master_state_all_will_use } //DOESN'T WORKS
{ this.getMasterState() } //DOESN'T WORKS
{ this.state.for_me_only } //WORKS
{ this.commonFunction() } //WORKS
</span>
}
}
I need to access Parent state and local state.
React allow function super calling but not merging state. I've tried on google to look for some "super" or "parent" keyword to access parent state but it seems it doesn't exists.
At runtime, Son component has no scope of Father's state.
Is this possibile?
I'm not sure if it is what are you looking for, but it's close to it:
class Parent extends React.Component {
constructor(props){
super(props)
this.state = { isParentState: true };
// needed for getting access to parent state
this.getMasterState = this.getMasterState.bind(this)
}
getMasterState(){
return this.state;
}
}
class Enhancer extends Parent {
constructor(props){
super(props);
// get parent state via super keyword
const parentState = super.getMasterState();
this.state = {
isChildState: true,
...parentState
}
}
render() {
return <div>
Merged state: { JSON.stringify(this.state)}
</div>
}
}
Worked example.
Hope it helps
I'm sure it's possible, but you DO NOT want to do this.
Anything you are trying to do with inheritance can be done with composition.
In you case, your "parent component" will pass any information the children may need as props
class Parent extends React.component
{
constructor(props){
this.state = { parentState : 0 }
this.parentFunction= this.commonFunction.bind(this);
}
parentFunction() { console.log("parentFunction()"); }
render() {
return (
<Child
parentFunction={parentFuction}
parentState={this.parentState}
/>
)
}
}
https://reactjs.org/docs/composition-vs-inheritance.html
EDIT
In React, inheritance is almost NEVER the answer.
Now if you're looking for a a way to reuse method logic, why not abstract the method to a helper file?
If that still doesn't work, perhaps a Higher Order Component (HOC) will do the trick.
Here's an example of a simple HOC:
const withCommonFunction = (WrappedComponent) => {
return class extends React.Component {
commonFunction() {
console.log("I'm a common function that is needed in many components!");
}
render() {
return (
<WrappedComponent commonFunction={this.props.commonFunction} />
);
}
}
}
Then you wrap whichever component you want to have the same logic with the HOC.
const Child = withCommenFunction(Child);
This is typically used to help reuse logic that would otherwise be implemented the same in different components

React child component can't get props.object

My parent component is like this:
export default class MobileCompo extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null,
datasets: {}
};
this.get_data = this.get_data.bind(this);
}
componentWillMount() {
this.get_data();
}
async get_data() {
const ret = post_api_and_return_data();
const content={};
ret.result.gsm.forEach((val, index) => {
content[val.city].push()
});
this.setState({data: ret.result.gsm, datasets: content});
}
render() {
console.log(this.state)
// I can see the value of `datasets` object
return (
<div>
<TableElement dict={d} content={this.state.data} />
<BubbleGraph maindata={this.state.datasets} labels="something"/>
</div>
)
}
}
child component:
export default class BubbleGraph extends React.Component {
constructor(props) {
super(props);
this.state = {
finalData: {datasets: []}
};
console.log(this.props);
// here I can't get this.props.maindata,it's always null,but I can get labels.It's confusing me!
}
componentWillMount() {
sortDict(this.props.maindata).forEach((val, index) => {
let tmpModel = {
label: '',
data: null
};
this.state.finalData.datasets.push(tmpModel)
});
}
render() {
return (
<div>
<h2>{this.props.labels}</h2>
<Bubble data={this.state.finalData}/>
</div>
);
}
}
I tried many times,but still don't work,I thought the reason is about await/async,but TableElement works well,also BubbleGraph can get labels.
I also tried to give a constant to datasets but the child component still can't get it.And I used this:
this.setState({ datasets: a});
BubbleGraph works.So I can't set two states at async method?
It is weird,am I missing something?
Any help would be great appreciate!
Add componentWillReceiveProps inside child componenet, and check do you get data.
componentWillReceiveProps(newProps)
{
console.log(newProps.maindata)
}
If yes, the reason is constructor methos is called only one time. On next setState on parent component,componentWillReceiveProps () method of child component receives new props. This method is not called on initial render.
Few Changes in Child component:
*As per DOC, Never mutate state variable directly by this.state.a='' or this.state.a.push(), always use setState to update the state values.
*use componentwillrecieveprops it will get called on whenever any change happen to props values, so you can avoid the asyn also, whenever you do the changes in state of parent component all the child component will get the updates values.
Use this child component:
export default class BubbleGraph extends React.Component {
constructor(props) {
super(props);
this.state = {
finalData: {datasets: []}
};
}
componentWillReceiveProps(newData) {
let data = sortDict(newData.maindata).map((val, index) => {
return {
label: '',
data: null
};
});
let finalData = JSON.parse(JSON.stringify(this.state.finalData));
finalData.datasets = finalData.datasets.concat(data);
this.setState({finalData});
}
render() {
return (
<div>
<h2>{this.props.labels}</h2>
<Bubble data={this.state.finalData}/>
</div>
);
}
}

Resources