This is a conceptual question in that I'm trying to understand the best way to handle tabular data in react without using any special component or library.
I have data in a html table created dynamically in my child component. The data comes from the parent component. Some of the columns have editable content that I trigger with an edit button to re-render a version of the table that has inline text boxes for all rows of the columns that are editable.
When I change the content of the text box, I want to be able to click on my save button and have all the rows get saved.
The save and edit buttons are not inline on the table, but just sit outside the table in my component.
How do I access the html table in my child component from the the parent component to read all the rows and save the values in the textboxes to a data store?
Here is a snippet of code where I'm attempting to build the select list dynamically. I'm having trouble with some syntax errors and it is not working, but it gives an idea of what I'm trying to do.
I'm passing in the category and the transaction id. I want to add the select list to each category cell in every row in my table when the edit mode is selected. The transaction id is my solution for having the index of the current row available on the list by adding 1 to the transaction id. I will then use the selected index - 1 to get the transaction id for updating the corresponding records category. This may be a hack, but I can't think of the right way or better way to link the transaction to the select list.
renderCategory(category,transid){
console.log(category);
if (this.props.editMode === true){
return <div>
<select id="selCategory" onChange={this.props.onCategoryChange}>
const arrCategory = ["Category1","Category1","Category2","Category3","Category4","Category5","Category6","Category7"];
var i;
for (i = 1;arrCategory.length+1;i++){
<option value={transid+1}>{arrCategory[i-1]}</option>
if(arrCategory[i-1] === category) {
selectedIndex = i-1;
}
}
</select>
</div>
}
else {
return category
}
}
Here I have the code in the parent for handling the onChange event of the select list in my child.
handleCategoryChange(e) {
// this will have to be changed because I'm using the index value to store the transaction id
alert("The Child HTML is: " + e.target.innerHTML + "The selected value is: " + e.target.options[e.target.selectedIndex].value);
//// update the date which is in parent state to reflect the new value for category for the passed in transaction id (the problem is I don't know what the transaction id is...)
}
To achieve this, you need to do the following
In your parent component:
Maintain a state in your parent component which will store the data that has to be rendered in the child component.
Write a function in parent component which will update the state(i.e. the data to be rendered in the child component).
Then pass the data in your parent component's state and the state update function to child component via props.
In your child component:
Retrieve the data and the function passed by the parent component from the props of the child component.
Use the data to populate your table and to each editable box's input, pass an onChange and provide it the reference of the function passed from your parent component.
Here is a small snippet to take reference from:
class Parent extends Component {
constructor() {
super()
this.state = {
data: ''
}
}
//update your state here
stateUpdateHandler = (value) => {
this.setState({data: value})
}
render () {
return(
<Child data={this.state.data} stateUpdateHandler={stateUpdateHandler} />
)
}
}
class Child extends Component {
render() {
const {data, stateUpdateHandler}
return(
<div>
<input
type='text' value={d.value}
onChange={(e) => stateUpdateHandler(e.target.value)} />
</div>
)
}
}
EDIT: This is how you should handle onChange event
onCategoryChange: function(event) {
console.log(event.target.value) //This would have the value of transaction provided in the value attribute of option tag
}
If you want to get the transaction id and not the value in the value attribute of the option tag, you will have to change your select tag and write it like this:
<select id="" onChange={function() {this.props.onCategoryChange(transid)}}>
Related
I have a bunch of child components being initialized in a map function in ComponentDidMount:
this.mappedApplications = this.props.applications.map((application, key) =>
<ApplicationCard
selectedYear={this.state.openApplicationYear}
applicationYear={application.application_year}
handleArrowClick={this.handleApplicationCardArrowClick}
key={key}
/>, this
);
this.setState({
applications: this.mappedApplications,
});
When the state.openApplicationYear changes I want it to change for all of the Application Cards. I do a check in the application card show info if the selected year matches the application year, with the goal that only one card will be open at a time. The cards have dropdown arrows that allow the user to open or close them. Right now the parent's openApplicationYear is changing correctly, but the children's aren't updating.
Here is the render function if it helps:
render() {
return (
<div className="dashboard__card">
<h3 className="mb-3">Applications</h3>
{this.state.applications}
</div>
)
}
I have bound "this" to the map function as seen above. Is there anything else I need to do to update the children when the parent's state changes?
I'm writing a simple calendar application that uses a common layout to wrap different views of events (month view shows a larger calendar with all the days of the month and events for each day, week view just shows a vertical list of events for that week, etc.). The common layout includes a calendar picker control for selecting the date, and then a list of event categories that can be checked or unchecked to show events relating to sports, entertainment, etc.
When the layout mounts, I'm calling an async Redux action creator to get the list of event categories from the database. When those are retrieved, they're saved in a Redux store with a property of Selected set to true, since they're all selected at initial load.
async componentWillMount() {
await this.props.getEventTypes();
}
When the month view, which is a child of the layout view, mounts, it's grabbing all the events for the given month. Part of the selection process of getting those events is sending the list of selected event categories to the backend so it only gets events from the selected categories.
async componentWillMount() {
await this.props.getWeeks();
}
The problem is, the selected categories list is always empty when the month view goes to grab the events for the month. So it's not going to select anything since no categories are selected.
It seems the only way this can be happening is if the child component is mounting first, or if the parent component is taking so long to get the event categories that the getWeeks process finishes first (this is unlikely as the process to grab the weeks and days and their events is much more involved than just selecting the event category list).
So, how can I make sure the parent component grabs the event categories from the database and puts them in the Redux store before the child component selects its events?
I know one way, probably the best way, to do this would be to have the list of event categories render into the page on the server side, so it's just already present at initial load. I'll probably end up doing it that way, but I'd also like to know how to do it all through client-side actions, in case I need to do it that way for some reason in the future.
You can try like this
Set isDataLoaded when data is available.
Use ternary operator for conditional rendering.
In you render
return(
<>
....
{ isDataLoaded ? <ChildComponent /> : null }
....other sutff
</>
);
Use can also use the && operator
return(
<>
....
{ isDataLoaded && <ChildComponent /> }
....other sutff
</>
);
You can integrate componentDidUpdate() and use it to render your child-components in a somewhat synchronous flow.
Let's say the structure of your Parent Component should look something like the following:
Parent
class Parent extends React.Component{
state = {
renderChildren: false
}
async componentDidMount() {
await this.props.getEventTypes();
}
componentDidUpdate(prevProps){
if(this.props.yourUpdatedReducer !== prevProps.yourUpdatedReducer){
this.setState({
renderChildren: true
})
}
}
render(){
const { renderChildren } = this.state
return(
{ renderChildren ? <Child/> : "Loading" }
)
}
}
You want a key in your state that determines whether you should
render the Child component.
In componentDidMount(), you call the action-creator function, when
it completes, you get updated props.
With updated props, you trigger componentDidUpdate(), where you
check the values in your reducer. If the values are
different that means you got the updated data from your database, so
everything has loaded.
Great, so now you want to mount your Child component, so you
update-state, setting renderChildren to true, thus re-rendering the
Parent component. Now Child gets rendered and should behave as expected.
In my react app component, I have a table on page with some list of data. On clicking any row on that table it fetches the details of that particular table row via its id and routing to another link like '/parent/:id' using the same parent component. On refreshing the page of single row details, it's taking me back to parent table data i.e on /parent page, but the URL is still /parent/:id.
I want to retain the /parent/:Id page even on refreshing.
Any leads?
Here is a general approach to such scenarios. I use search params in such a way for the url:
parent?rowId=2
Then in the parent component you check if the search param includes the rowId as a string. If so show the detail content and hide parent content, if not, show parent and hide detail content.
Parent Component would be something similar to this:
render () {
const { search } = this.props.location
const shouldDisplayRowDetail = search.includes('rowId')
return (
<div>
{
!shouldDisplayRowDetail &&
<div> Parent content </div>
}
{
shouldDisplayRowDetail &&
<div> Row content </div>
}
</div>
)
}
So my application has 3 drop-downs, a pagination component and a table.
Two drop-downs receive their options from rest call.
eg: State and City.
Based on the state selection the city dropdown makes rest call to fetch city.
Based on the selection made in the drop-downs the data in the table updates by making rest call.
The react.org example talks about lifting the state up. Hence the parent component which displays all the three drop-downs and table maintains the state of keeping track of which state, city and date is selected and what is the data that is being displayed in the table. Every-time the dropdown selection changes the parent state updates and re-fetches the new data.
Also the the table data updates every few seconds by sending the selected
state, city and date.
The question that I have is should the rest call to fetch the information regarding the dropdown options be in parent component or respective drop down components so that I can reuse the drop down in other page and do I need the dropdown to have there own state to keep track what is the selected value?
Should the rest call to fetch the information regarding the dropdown
options ?
The rest call to fetch the data should be part of the parent component only.
It makes sense to keep the dropdown component as a pure component which receives the options values as props from it's parent.
Following this method would really make your dropdown component as reusable component who only job is display the options and pass back the selected value to it's parent via a callback function passed as prop to the dropdown component.
Do I need the dropdown to have there own state to keep track what is
the selected value?
No, you can pass the selected value back to the parent using a callback function.
Some Code
Parent Component -->
constructor(){
this.state = {
stateOptions: null,
cityOptions: null,
selectedState: null,
selectedCity: null
}
}
componentDidMount(){
fetch.state.api => {
this.setState({
stateOptions: stateDataFromApi
})
}
}
handleStateSelection = (selectedState) => {
fetch.city.api => {
this.setState({
selectedState: selectedState,
cityOptions: cityDataFromApi
})
}
}
handleCitySelection = (selectedCity) => {
this.setState({
selectedCity: selectedCity
})
}
<div className="parentComponent">
<Dropdown
type="state"
handleStateSelection="this.handleStateSelection"
data={this.state.stateOptions}
/>
<Dropdown
type="city"
handleStateSelection="this.handleCitySelection"
data={this.state.cityOptions}
/>
<Table data={this.tableData} />
..
..
</div>
in my form i have a few dropdown components. Whenever first dropdown option changes i want to update props for the second dropdown and rerender it. My code looks like this
handleProjectChange(option) {
//this.setState({ selectedProject: option })
this.refs.phase.props = option.phases;
//this.refs.forceUpdate()
this.refs.phase.render()
}
render() {
var projectOptions = this.projectOptions
var defaultProjectOption = this.state.selectedProject
var phaseOptions = defaultProjectOption.phaseOptions
var defaultPhaseOption = phaseOptions[0]
var workTypeOptions = api.workTypes().map(x => { return { value: x, label: x } })
var defaultWorkTypeOption = workTypeOptions[0]
return (
<div>
<Dropdown ref='project' options={projectOptions} value={defaultProjectOption} onChange={this.handleProjectChange.bind(this)} />
<Dropdown ref='phase' options={phaseOptions} value={defaultPhaseOption} />
<Dropdown options={workTypeOptions} value={defaultWorkTypeOption} />
<button className="btn btn-primary" onClick={this.handleAddClick.bind(this)}>Add</button>
</div>
)
}
But props are not changed, so it rerenders the same options. At the moment im just rerendering entire form by setting new state on it. Is there any way to rerender only one child/Dropdown with new props?
The way to do this is to put the selected option in first dropdown selectedProject in state.
And inside your render function, fetch/ populate the options in the second dropdown, dependent on the selected project.
Flow will then be:
User selects an option in the first dropdown.
This triggers handleProjectChange()
Inside handleProjectChange(), the newly selected option is put in state, by a this.setState() call
Because state changed, react re-runs the entire render() function.
Under the hood, react figures out that only the second dropdown has changed, so react will only re-render the second drop-down on your screen/ in the DOM.
Although React does have a reconciliation algorithm that dynamically checks whether each component should be rerenader or not in every rendering of its parent, it doesn't always work as we intended.
https://reactjs.org/docs/reconciliation.html
For this kind of issues, you have two options. You can use either React.pureComponent or React.useMemo().