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>
Related
I want to reload the UserPlaylist tag after running the onChangeTracks() function in order to update it's contents but I'm not sure how to re-execute a specific tag if possible.
Parent Component:
render(){
return(
<li><UserPlaylist onChange={this.onChangeTracks}/></li>
);
}
UserPlaylist Component (Child Component):
render() {
return(
window.addEventListener('DOMContentLoaded', (event) => {
this.getPlaylists() //Have tracks load immediately
}),
<select value={"DEFAULT"} onChange={this.props.onChange}>
<option value="DEFAULT"> Add to Playlist </option>
<option value="new"> New Playlist </option>
{
this.state.users_playlists.map((playlist, index) => (
<option key={index} value={playlist.id}> { playlist.name }</option>
))
}
</select>
);
}
Components will rerender every time their state is updated. So you should update the state of your Parent component whenever the this.onChangeTracks function is invoked. Since this.state.currentTrack is being passed in as a prop to UserPlaylist, once it is updated via a call to setState the UserPlaylist component will receive new props and should rerender those new props accordingly.
Example:
onChangeTracks(val) {
...
this.setState({currentTrack: val});
...
}
EDIT
Here's a Codesandbox that updates the "New Playlist" select element whenever the onChange event is called.
Here's how it works: First, the useEffect hook fires and loads in data from the examplePlaylists variable. In your app, you should populate this with the data you have saved in your database or localStorage, etc. This data is passed to the playlists state which uses the useState hook. Whenever a person selects an option from the dropdown menu, the onChange event is fired and calls the handleUpdatePlaylists function. This function first prompts the user to enter the name of their new playlist. It then updates the playlists state through the updatePlaylistsfunction. Now, since the state has changed, the component will rerender and map over all the playlists, displaying their names in the dropdown menu. All without needing to refresh the page.
Of course, since I don't have a DB to save these to, the new playlists will disappear on page refresh. But you can write logic to save them however you wish.
And while this works, I think that you should look to using buttons and the onClick event instead of a select element to handle this logic. Since a new playlist is created whenever an option is selected, then clicking on the name of any playlist will also create them. This doesn't appear to be intended functionality.
So to summarize: handle updates to your components by changing their state. Once state changes, the components will rerender with their new state available for you to work with.
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.
I have a react component that is responsible for listing out data and if the user clicks on a particular data item, it renders a popup with the data the user clicked passed to it. The popup is defined in a separate component but a single instance of the popup is defined in listing component as follows:
render(){
return(
...
{tasks.map((task, index) => {
return (
<p><a onClick={() => self.edit(task.id)}>{task.name}</a></p>
);
})}
<EditTaskPopup show={self.state.showEditPopup} onClose={self.onClosePopup} task={self.state.editData} />
...
)
}
The edit function, packages up the data and sets the component's state so that the data in included in the editData variable and the popup is shown as follows:
self.setState({showEditPopup: true, editData: tasks[x]});
This all works fine but my question is how I should correctly receive that data in the popup container, EditTaskPopup. The constructor of EditTaskPopup fire off when the parent component loads, so no user interaction has occurred, so no value is passed in. Same holds true for componentDidMount. I can see the correct value being passed in when the componentDidUpdate fires off, but that also fires off during the normal operation of the popup where I'm collecting information about what the user is typing within the popup and placing those values in state. Is there an event in a component that only fires off when a parent component changes the parameters passed into it but doesn't fire off when state changes within the component itself?
Try utilising lifecycle method: https://reactjs.org/docs/react-component.html#componentdidupdate
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)}}>
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().