I am using React Table Library in Web Application.
I am passing a component as a column to the table. Columns and data for the table looks like below.
columns = [
{
Header: "Name",
accessor: "name"
},
{
Header: "Action",
accessor: "action"
}
];
sampleData = [
{
id: 1,
name: "Product one",
action: (
<TestComponent
id={1}
inProgress={false}
onClickHandler={id => this.onClickHandler(id)}
/>
)
},
{
id: 2,
name: "Product two",
action: (
<TestComponent
id={2}
inProgress={false}
onClickHandler={id => this.onClickHandler(id)}
/>
)
}
];
My TestComponent
Looks like below.
const TestComponent = ({ id, inProgress, onClickHandler }) => {
return (
<div>
{inProgress ? (
<p>In Progress </p>
) : (
<button onClick={() => onClickHandler(id)}>click me</button>
)}
</div>
);
};
What my purpose is, When user click on click me
button it needs to call a Backend API
. At that time inProgress
prop need to be true
and it should pass to the table and need text as In Progress until API call completed.
I could be able to do it changing state
as below.
onClickHandler(id) {
const newData = this.sampleData.map(data => {
if (data.id === id) {
return {
...data,
action: (
<TestComponent
id={1}
inProgress={true}
onClickHandler={id => this.onClickHandler(id)}
/>
)
};
}
return data;
});
this.setState({ data: newData });
// Here I Call the Backend API and after complete that need to show the click me button again.
setTimeout(() => {
this.setState({ data: this.sampleData });
}, 3000);
}
I could be able to achieve what I need But I am not satisfied the way I am changing the state
. I need to know is there a better way of doing this without changing state
like this.
You can use this StackBlitz Link for giving me a better solution. Thanks.
Instead of passing inProgress
prop to TestComponent
, you could maintain a local state in the TestComponent
that is used to determine whether to show progress text or a button and only pass the id
and onClickHanlder
props to TestComponent
.
When button is clicked in TestComponent
, you could set the the local state of TestComponent
to show the progress text and then call the onClickHandler
function passed as prop, passing in the id
prop and a callback function as arguments. This callback function will be called when API request is completed. This callback function is defined inside TestComponent
and only toggles the local state of the TestComponent
to hide the progress text and show the button again.
Change your TestComponent
to as shown below:
const TestComponent = ({ id, onClickHandler }) => {
const [showProgress, setShowProgress] = React.useState(false);
const toggleShowProgress = () => {
setShowProgress(showProgress => !showProgress);
};
const handleClick = () => {
setShowProgress(true);
onClickHandler(id, toggleShowProgress);
};
return (
<div>
{showProgress ? (
<p>In Progress </p>
) : (
<button onClick={handleClick}>click me</button>
)}
</div>
);
};
i have used useState
hook to maintain local state of the TestComponent
as it is a functional component but you could use the same logic in a class component as well.
Change the TestComponent
in sampleData
array to only be passed two props, id
and onClickHandler
.
{
id: 1,
name: "Product one",
action: <TestComponent id={1} onClickHandler={this.onClickHandler} />
}
and change onClickHandler
method in App
component to:
onClickHandler(id, callback) {
// make the api request, call 'callback' function when request is completed
setTimeout(() => {
callback();
}, 3000);
}
Alternatively, you could make onClickHandler
function in App
component to return a Promise
that is fulfilled when API request completes. This way you don't have to pass a callback function from TestComponent
to onClickHandler
method in App
component.
Change onClickHandler
method to:
onClickHandler(id) {
return new Promise((resolve, reject) => {
setTimeout(resolve, 3000);
});
}
and change TestComponent
to:
const TestComponent = ({ id, onClickHandler }) => {
const [showProgress, setShowProgress] = useState(false);
const toggleShowProgress = () => {
setShowProgress(showProgress => !showProgress);
};
const handleClick = () => {
setShowProgress(true);
onClickHandler(id)
.then(toggleShowProgress)
.catch(error => {
toggleShowProgress();
// handle the error
});
};
return (
<div>
{showProgress ? (
<p>In Progress </p>
) : (
<button onClick={handleClick}>click me</button>
)}
</div>
);
};