I have a component that returns an object called Progress
, inside of which is an array called Results
. This array has objects with various properties, one of which is called total
{
Progress: {
count: 100,
results: [
{total: 4, ...},
{total: 10, ...},
...
]
}
}
The component, Dashboard
, gets the data from state and maps it the Progress property.
export class Dashboard extends Component {
static propTypes = {
progress: PropTypes.object.isRequired,
getProgress: PropTypes.func.isRequired,
totalResults: PropTypes.number.isRequired
}
componentDidMount() {
this.props.getProgress()
}
...
}
const selectProgress = state => state.progressReducer.progress
const mapStateToProps = state => ({
progress: selectProgress(state),
})
export default connect(mapStateToProps, { getProgress })(Dashboard)
The issue I have now is how can I add a new property which is derived from progress?
I understand I need to use a Selector but I cannot see where/how to do this.
For example, I know I can do something trivial (and pointless) like this:
const mapStateToProps = state => ({
progress: selectProgress(state),
count: selectProgress(state).count
})
which adds another property count
to the component (yes it's just duplicated the property inside progress, hence why it is pointless).
What I need to do is something like this:
const mapStateToProps = state => ({
progress: selectProgress(state),
resultsTotal: <loop through the results array and sum the property total>
})
1 - What I have tried
I tried this even though I understand it isn't meant to be this way. This is to illustrate hopefully what I am trying to do - AFTER I've got progress, pass it to some function to calculate the total and return that as a property to the component:
const selectResults = progress => {
progress.results.reduce((acc, result) => {
acc + result.total
}, 0)
}
const mapStateToProps = state => ({
progress: selectProgress(state),
totalResults: selectResults(progress)
})
2 - What I have tried
I thought this would have worked, by basically letting the render view call function at the point needed in the JSX:
export class Dashboard extends Component {
static propTypes = {
progress: PropTypes.object.isRequired,
getProgress: PropTypes.func.isRequired,
}
componentDidMount() {
this.props.getProgress()
}
totalResults() {
if (this.props.progress.results)
return this.props.progress.results.reduce((acc, result) => {
acc + result.total
}, 0)
}
render() {
...
<SummaryCard title='Students' value={this.totalResults()} />
...
}
}
I am now wondering why this didn't work - I had to add this line:
if (this.props.progress.results)
because progress is of course empty when this function executes (ie I guess because it executes when the component first mounts, and the store has not returned the data yet).
One solution that I found to this problem is to use the excellent reselect library. From their github page:
So I had to create a second selector and chain it to the first one.
Below you can see that
progressSelector
will pass its result (theprogress
data) onto the next selector in the chain (totalResultsSelector
):Here is the full component: