I am new to react.js and facing problems while performing the below task.
When running following the code, it shows setState
can't be used and the app breaks. I have these two objects, obj2
generates a table 3 columns
. I need to make each cell of definition
column which are not null
clickable which then land me to the team_wiki
link which is present in obj1
.
obj1: {
providers : [
{team_name : "XYZ", team_wiki : "https://example.com", team_id : 1},
{team_name : "ABC", team_wiki : "null", team_id : 2},
{team_name : "LMN", team_wiki : "https://example2.com", team_id : 3},
{team_name : "MNO", team_wiki : "https://example3.com", team_id : 4}
]
}
obj2: {
products : [
{team_name : "XYZ", definition : "this team handles XYZ products", metric_id : 101},
{team_name : "ABC", definition : "this team handles ABC products", metric_id : 201},
{team_name : "LMN", definition : "this team handles LMN products", metric_id : 301},
{team_name : "MNO", definition : "this team handles MNO products", metric_id : 401}
]
}
Code:
state = {
API_DATA : {},
TEAM_WIKI : ''
}
componentDidMount(){
// fetch the obj1 data and sets it to the state called API_DATA
}
wikiLink = (TEAM_NAME) {
// getting TEAM_NAME from a component inside the render method
const wiki = this.state.API_DATA.map(provider => {
if (provider.team_name = TEAM_NAME && provider.team_wiki !== null) {
return provider.team_wiki
}
})
.map(link => {
if (link !== undefined) {
return link
}
})
this.setState({
// TEAM_WIKI is a state { its a string } where i want to store the
//team_wiki at last
TEAM_WIKI : wiki
})
}
render() {
return (
// i want to use it something like this
< href="this.state.TEAM_WIKI" onClick={this.wikiLink(TEAM_NAME)} />
)
}
It feels to me that when the first ever render happens, this.wikiLink
is being called, not passed as reference. Why do I think so? Because I see round brackets there. The body of this method contains a call to this.setState
, which means the state of the component is being swapped on render time, and it is a violation of component lifecycle.
Consider a case:
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
buttonClicked: false,
};
this.handleClickButton = this.handleClickButton.bind(this);
}
handleClickButton() {
this.setState({
buttonClicked: true,
});
}
render() {
return (
<button onClick={this.handleClickButton()}>click me</button>
);
}
}
There is a problem! The expression this.handleClickButton()
is a function call. Within it, there is a state change instruction. When the code is trying to do that, React shows a warning:
Warning: setState(...): Cannot update during an existing state transition (such as within
render
or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved tocomponentWillMount
.
What we really want instead is to call the function this.handleClickButton
on button click. To do so, we need to pass a reference to this function to the onClick
prop, like this:
render() {
return (
<button onClick={this.handleClickButton}>click me</button>
);
}
notice the absence of round brackets.
So what should you do then?
There are two options:
Instead of writing
render() {
return (
// i want to use it something like this
<a href="this.state.TEAM_WIKI" onClick={this.wikiLink(TEAM_NAME)} />
);
}
do
render() {
return (
// i want to use it something like this
<a href="this.state.TEAM_WIKI" onClick={() => this.wikiLink(TEAM_NAME)} />
);
}
This way, a reference to an anonymous function declared in-place is passed per each call to render
method of the component instance. This function is then going to be called at some unpredictable point in time when the user decides to click, that is, dispatch the MouseEvent of type "click".
The concept of function returning a function is often referred to as partial application or, in cases similar to this more often, a closure. So the wikiLink
function would then instead of this:
wikiLink(TEAM_NAME) {
// getting TEAM_NAME from a component inside the render method
const wiki = this.state.API_DATA.map(provider => {
if ((provider.team_name = TEAM_NAME && provider.team_wiki !== null)) {
return provider.team_wiki;
}
}).map(link => {
if (link !== undefined) {
return link;
}
});
this.setState({
// TEAM_WIKI is a state { its a string } where i want to store the
//team_wiki at last
TEAM_WIKI: wiki
});
}
look like this:
wikiLink(TEAM_NAME) {
return () => {
// getting TEAM_NAME from a component inside the render method
const wiki = this.state.API_DATA.map(provider => {
if ((provider.team_name = TEAM_NAME && provider.team_wiki !== null)) {
return provider.team_wiki;
}
}).map(link => {
if (link !== undefined) {
return link;
}
});
this.setState({
// TEAM_WIKI is a state { its a string } where i want to store the
//team_wiki at last
TEAM_WIKI: wiki
});
};
}
So that when it is called, it returns a new function. Because onClick
prop is expected to receive a function, we solve the problem of type matching. Because we call wikiLink
with some argument TEAM_NAME
and this argument is being applied every time that function is executed, we solve the problem of retaining context. Isn't this awesome!
To get a better idea of using a function that returns a function, check out this Codesandbox, lines 16 to 23. Hope it will give some idea of how to leverage closures in situations when you need to manage state of a React component.
Cheers!