Let's say we got two different React components. One contains reports with dates, the other should show employees that worked that particular month.
So depending on what reports month was clicked, I need to be able to show those employees, but in a second component.
I'm able to get the date that was clicked in the first one but in order to know which employees to show I need to compare that data (from the 1st component) with employees data (second component).
The big question here is - HOW CAN I TRANSFER THAT NEWLY CONSTRUCTED (onClick - Custom function)EVENTS DATA TO THAT SECOND COMPONENT SO I CAN COMPARE THEM ??
You can create a "Parent" component which will render your two components.
The Parent component will have the selected date in the state.
class Parent extends Component {
constructor() {
this.handleDateChange = this.handleDateChange.bind(this);
this.state = { date: null };
}
handleDataChange(date) {
this.setState({ date });
}
render() {
return (
<div>
<Component1 onDateChange={this.handeDataChange} />
<Component2 date={this.state.date} />
</div>
);
}
}
You have to update your Component1 to receive onDateChange, and you have to call that function when the date is updated:
// where the date is updated
this.props.onDateChange(newDate);
Also you have to update your Component2 to receive date (the selected date) which you can use to filter your employees:
// maybe in the render function... you will know the selected date with this.props.date. For example you could do something like this:
const filtered = this.employees.filter(employee => employee.date === this.props.date);
How does this work?
when you select your date in your first component, it will call handleDateChange
... It will update Parent's state
... then Parent's render function will be called (because the state changed)
... then it will pass the new date (stored in the state) to the second component.
Contain the component within a common parent component that's then able to act as a broker for the relevant data.
Often, several components need to reflect the same changing data. We recommend lifting the shared state up to their closest common ancestor.
[From: lifting state up]
This is simplified but something like:
class Employees extends Component {
state = {
employees: []
}
async componentDidMount() {
const { clickedDate } = this.props
const employees = await fetchEmployees(clickedDate) // or whatever
this.setState({ employees })
}
render() {
const { employees } = this.state
if (employees.length === 0) {
return
<p>Loading...</p>
}
return (
<div className='employees'>
{
employees.map(employee => (
<p>{employee}</p>
))
}
</div>
)
}
}
const Reports = ({ dates, setClickedDate }) => (
<div className='reports'>
{
dates.map(date => (
<p onClick={() => setClickedDate(date)}>{date}</p>
))
}
</div>
)
class Parent extends Component {
state = {
clickedDate: undefined,
dates: ['dates', 'from', 'somewhere']
}
setClickedDate = clickedDate => this.setState({ clickedDate })
render() {
const { clickedDate } = this.state
return [
<Reports dates={dates} setClickedDate={this.setClickedDate} />,
<Employees clickedDate={clickedDate} />
]
}
}
Related
I have a table of schedules that is rendered by a dropdown. Each schedule can then be marked for export via a slider, this will store the schedule id in scheduleIdsToExport and show that schedule in the export table.
But if I change the Select Query dropdown, which renders more schedules specific to that query, the schedules marked for export from the previous query disappear from the table. I want the schedules marked for export to persist in the table no matter what query is selected from the dropdown.
So I'm thinking I need to have something in my slider function to update state with the all the schedule objects marked for export and have them persist in the exported table. I'm not exactly sure how to go about storing all the schedules to keep them in the exported table and have the scheduleIdsToExport array also keep the id's of each schedule
slider = ({ id, isExported }) => {
if (isExported === true) {
this.setState(
{
scheduleIdsToExport: [id, ...this.state.scheduleIdsToExport]
},
() => {
console.log(this.state.scheduleIdsToExport);
}
);
} else {
const newArray = this.state.scheduleIdsToExport.filter(
storedId => storedId !== id
);
this.setState(
{
scheduleIdsToExport: newArray
},
() => {
console.log(this.state.scheduleIdsToExport);
}
);
}
};
The sandbox here will provide further explanation on what is happening.
This is just chaotic!
The problem : Keep track from multiples list of items(schedules) that will eventually be added to another list schedulesToExport
The Solution :
Create a parent component that reflects the previously described state
class Container extends Component{
state ={
querys :[
['foo','lore ipsum','it\'s never lupus'],
['bar','ipsumlorem', 'take the canolli']
],
selectedQuery : 0,
schedulesToExport : []
}
}
Now we have a list of lists, that can be interpreted as a list of querys containing a list of schedules
Render a select element to reflect each query:
render(){
const { querys, selectedQuery } = this.state
const options = querys.map((_, index) => (<option value={index}> Query: {index + 1}</option>))
return(
<div>
<select onChange={this.selectQuery}>
{options}
</select>
{
querys[selectedQuery].map(schedule => (
<button onClick={() => this.selectSchedule(index)}> Schedule: {schedule} </button>
))
}
</div>
)
}
What's happening? We are just rendering the selected query by it's index and showing all it's respective schedules.
Implement the selectQuery and selectSchedule methods which will add the selected schedule in the list to export:
selectQuery = e => this.setState({selectedQuery : e.target.value})
selectSchedule = index => {
const { selectedQuery } = this.state
const selected = this.state.querys[selectedQuery][index]
this.setState({
schedulesToExport: this.state.schedulesToExport.concat(selected)
})
}
That's it, now you a have a list of querys being displayed conditionally rendered selectedQuery props is just a index, but could be a property's name. You only see schedules from the current selected query, so when you click on schedule we just return it's index, and the state.querys[selectedQuery][index] will be your selected schedule, that is securely store in the state on a separated list.
I have updated your sandbox here.
In essence, it does not work in your example because of the following:
schedules
.filter(schedule =>
scheduleIdsToExport.includes(Number(schedule.id))
)
.map(schedule => {
return (
<Table.Row>
...
</Table.Row>
);
})
The value of schedules is always set to the current query, hence you end up showing schedules to export for the current query only.
A solution that changes very little of your code is to ditch scheduleIdsToExport altogether, and use schedulesToExport instead. Initially, we'll set schedulesToExport to an empty
object; we'll add schedules to it (keyed by schedule id) every time a schedule is selected - we'll delete schedules in the same way every time a schedule is unselected.
class App extends React.Component {
// ...
slider = ({ id, isExported }) => {
const obj = this.state.schedules.find(s => Number(s.id) === Number(id));
if (isExported === true) {
this.setState({
schedulesToExport: {
...this.state.schedulesToExport,
[id]: {
...obj
}
}
});
} else {
const newSchedulesToExport = this.state.schedulesToExport;
delete newSchedulesToExport[id];
this.setState({
schedulesToExport: newSchedulesToExport
});
}
};
// ...
}
You would then render the schedules to export as follows:
Object.keys(schedulesToExport).map(key => {
const schedule = schedulesToExport[key];
return (
<Table.Row>
...
</Table.Row>
);
})
Again, see more details in sandbox here.
ObjectList has an array of objects that get rendered as a list. When the user clicks a list item, that object is sent back to ObjectEditor so the user can view it and continue editing. The problem is that I'm not sure how to pass that object to ObjectEditor because the click event is taking place in ObjectList.
My initial solution was to pass it to ObjectEditor as props and use the componentWillReceiveProps method to update ObjectEditors state. However, that solution wasn't practical because I don't want it to update every time the props change. Is there a better way?
I'm new to React so I'd like to avoid using Redux for now until I've covered React.
I've heavily cut down the code for clarity.
ObjectList:
constructor(props){
super(props);
this.state = { objects: [
{title: '', items: [], anotherThing:''},
{title: '', items: [], anotherThing:''}
]}
}
viewObject = (index) => {
let object = {...this.state.object[index]};
// Then some code that passes the object to the ObjectEditor Component
}
render(){
return(
<div>
<li key={index} onClick={ () => this.viewObject(index) } >
// A list of titles from state
</li>
<ObjectEditor />
</div>
)
}
ObjectEditor:
constructor(props){
super(props);
this.state = {title:'', items: [], anotherThing:''}
}
// various event handlers that update the state based off form inputs
render(){
return(
<div>
// Various form fields which get pushed to state
<button>Save & Add New</button>
// function that maps through state and renders it to the page
</div>
)
}
}
My suggestion would be to have the parent component handle all the state and logic, and keep the ObjectEditor component a simple presentation component with no logic or state of its own. It would look a little something like this.
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
objects: [
{ title: '', items: [], anotherThing: '' },
{ title: '', items: [], anotherThing: '' }
],
editObject: {},
}
}
viewObject = (index) => {
let object = { ...this.state.object[index] };
this.setState({editObject: object}); // sets the state if the clicked item.
// Then some code that passes the object to the ObjectEditor Component
}
handleChange = (e) => {
// handle change
}
render() {
return (
<div>
<li key={index} onClick={() => this.viewObject(index)} >
// A list of titles from state
</li>
<ObjectEditor viewObject={this.state.viewObject} handleChange={this.handleChange} />
</div>
)
}
}
class ObjectEditor extends React.Component {
render() {
return (
// render some sort of editor
// display data based on the props passed down
// when user edits in the form, call up to the parent's change handler
);
}
}
In my componentDidMount(), I am calling an actionCreator in my redux file to do an API call to get a list of items. This list of items is then added into the redux store which I can access from my component via mapStateToProps.
const mapStateToProps = state => {
return {
list: state.list
};
};
So in my render(), I have:
render() {
const { list } = this.props;
}
Now, when the page loads, I need to run a function that needs to map over this list.
Let's say I have this method:
someFunction(list) {
// A function that makes use of list
}
But where do I call it? I must call it when the list is already available to me as my function will give me an error the list is undefined (if it's not yet available).
I also cannot invoke it in render (before the return statement) as it gives me an error that render() must be pure.
Is there another lifecycle method that I can use?
Just do this, and in redux store please make sure that initial state of list should be []
const mapStateToProps = state => {
return {
list: someFunction(state.list)
};
};
These are two ways you can play with received props from Redux
Do it in render
render() {
const { list } = this.props;
const items = list && list.map((item, index) => {
return <li key={item.id}>{item.value}</li>
});
return(
<div>
{items}
</div>
);
}
Or Do it in componentWillReceiveProps method if you are not using react 16.3 or greater
this.state = {
items: []
}
componentWillReceiveProps(nextProps){
if(nextProps.list != this.props.list){
const items = nextProps.list && nextProps.list.map((item, index) => {
return <li key={item.id}>{item.value}</li>
});
this.setState({items: items});
}
}
render() {
const {items} = this.state;
return(
<div>
{items}
</div>
);
}
You can also do it in componentDidMount if your Api call is placed in componentWillMount or receiving props from parent.
Should intermediate components control parts of state and call props passed to them or should state be lifted higher? I've been going back and forth whether to have the child component utilize local state or have it handled by higher component and pass additional props down.
In this limited example, I have a Main component. I display some data in this component and pass functions to filter the data to a child component. Though, main component doesn't necessarily need to know about when the menuOpen property is changed. However, I need to update menuOpen when handleCancel(), handleSave(), and handleButtonClick() are called.
handleCancel() and handleSave() both modify the data that is displayed so I declare them in the Main component.
Should I be passing all these props through from Main component or use intermediate components to handle smaller portions of local state but also call props from a parent (grandparent etc) component?
Main Component
//Parent component
class Main extends React.Component {
constructor() {
super();
this.state = {
checkBoxes: {
1: {
name: 'Apple',
isChecked: true,
},
//...
},
fruit: {
1: {
name: 'Apple',
},
//...
},
checkedBoxes: [],
};
this.baseState = JSON.stringify(this.state.checkBoxes);
this.fruitFilter = this.fruitFilter.bind(this);
this.handleSave = this.handleSave.bind(this);
this.handleChange = this.handleChange.bind(this);
this.resetState = this.resetState.bind(this);
}
resetState() {
this.setState({checkBoxes: JSON.parse(this.baseState)});
}
//populates the checkedboxs array with name to filter by
handleSave() {
const checkedBoxes = Object.keys(this.state.checkBoxes)
.filter(key => {
//....some logic
});
this.baseState = JSON.stringify(this.state.checkBoxes);
this.setState({checkedBoxes: checkedBoxes});
}
//handles the checkbox toggle
handleChange(e) {
const checkBoxes = {...this.state.checkBoxes};
checkBoxes[e.target.id].isChecked = e.target.checked;
this.setState({checkBoxes: checkBoxes});
}
//filteres the fruit - if nothing is checked return them all
fruitFilter(fruit) {
return Object.keys(fruit)
.filter(key => {
//...filter logic
})
}
render() {
const visibleFruits = this.fruitFilter(this.state.fruit);
return (
<div>
<Filter
resetState={this.resetState}
checkBoxes={this.state.checkBoxes}
handleSave={this.handleSave}
handleChange={this.handleChange}
/>
<div>
<h2>Filtered Fruit</h2>
{Object.keys(visibleFruits).map(key => {
return (
//... renders list of fruit
);
})}
</div>
</div>
);
}
}
Child Component
class Filter extends React.Component {
constructor(props) {
super(props);
this.state = {
menoOpen: false,
};
this.handleCancel = this.handleCancel.bind(this);
this.handleSave = this.handleSave.bind(this);
this.handleButtonClick = this.handleButtonClick.bind(this);
}
handleSave() {
this.setState({menuOpen: false});
this.props.handleSave();
}
handleCancel() {
this.setState({menuOpen: false});
this.props.resetState();
}
handleButtonClick() {
this.setState({menuOpen: !this.state.menuOpen});
}
render() {
return (
<div>
<button onClick={this.handleButtonClick}>Choose Fruits</button>
{this.state.menuOpen && (
<FilterMenu
checkBoxes={this.props.checkBoxes}
handleSave={this.handleSave}
handleCancel={this.handleCancel}
handleChange={this.props.handleChange}
/>
)}
</div>
);
}
}
Grandchild Component
const FilterMenu = ({checkBoxes, handleChange, handleCancel, handleSave}) => {
return (
<div>
{Object.keys(checkBoxes).map(key => {
return (
//... renders dropdown menu
);
})}
<button onClick={handleCancel}>Cancel</button>
<button onClick={handleSave}>Save</button>
</div>
);
};
Refine the separation of concerns and I think you'll like it better.
Define all checkbox event handlers in Filter.
Filter communications with Main via state only.
Don't force Main to evaluate UI components to set state.
Define Main state for Filter to use as needed to avoid the above.
Filter will construct the checkboxes.
Cancel and Save buttons seem like Filter level functions to me.
A FilterMenu component now seems pointless because it does not do anything. Perhaps in the larger architecture it is useful but you can always re-factor it out of Filter when needed
Filter component is the seam in the code that separates action from state.
State is not unnecessarily pushed further down.
Actual functionality is not unnecessarily pushed further up.
Coupling between Main and Filter is reduced. Filter has more reuse potential.
I have two DropTargets, ComponentA and ComponentB arranged in this structure:
<ComponentA
dropEvent={this.handleRootDropEvent}
>
<p>Displayed Categories </p>
{
this.state.categories.map((c, idx) =>
<ComponentB
key={idx}
parentCat={null}
thisCat={c}
level={c.level}
dropEvent={this.handleDropEvent}
/>
)
}
</ComponentA>
And the problem I'm having in particular is with the drop target spec drop function. Both of the DropTargets (for ComponentA and B) have the same drop function defined at the moment:
//ComponentB
const targetSpec = {
drop(props, monitor, component) {
const item = monitor.getItem()
if (props.thisCat.category_id == item.category_id) return
component.setState({ droppedItem: item })
component.setState({ droppedItem: null })
},
canDrop(props, monitor) {
const item = monitor.getItem()
if (props.thisCat.category_id == item.category_id) return false
return true
},
hover(props, monitor, component){
}
}
// Component A
const source = {
drop(props, monitor, component) {
const item = monitor.getItem()
component.setState({ droppedItem: item })
component.setState({ droppedItem: null })
}
}
(I can confirm that both are getting called on drop events, and that item is not null in ComponentA's drop function)
It seems that component.setState({}) actually changes the props of the component it's called on, or at least that's how I'm able to use it in the case of ComponentB:
componentWillReceiveProps = () => {
if (this.props.droppedItem) {
this.props.dropEvent(this.props.droppedItem, this.props.thisCat)
}
}
And that code works, I'm able to trigger a function in the parent component (or this case grandparent) of ComponentB by checking for the presence of those props that are set in the drop function.
However, in the same exact componentWillReceiveProps function definition in ComponentA, this.props.droppedItem is always undefined.
Any idea what I could try to get the props passed successfully? Am I misunderstanding React DnD's component.setState({}) API?