1

I've created some templates in Django, and I want to translate them to work in ReactJS. The thing that I'm struggling with the most is the regroup function. There's a number of ways I've thought of approaching it, but I think the easiest is probably to do it within the component. All I've managed to do is map the items which generates the entire list, but I need to have the items dynamically grouped before iterating each group.

In React I'd like to be able to apply a command like items.groupBy('start_time').groupBy('event_name').map(item =>

The output should be 'start_time', 'event_name', and then the rest of the data within each event group. Each 'start_time' will contain multiple events. I'd like to keep the code as concise as possible.

This is the Django template:

{% if event_list %}
<div id="accordian" class="panel-group" role="tablist" aria-multiselectable="true">
{% regroup event_list by start_time as start_time_list %}
{% for start_time in start_time_list %}
 <div class="row start-time">
    <div class="col">
        <h6 class="text-muted">{{ start_time.grouper|date:'  d-m-Y  H:i' }}</h6>
    </div>
</div>
{% regroup start_time.list by event as events_list_by_start_time %}
{% for event_name in events_list_by_start_time %}
<div class="panel panel-default">
<div class="card-header" id="{{ event_name.grouper|slugify }}">
<div class="panel-heading">
<h5 class="panel-title">
<a data-toggle="collapse" data-parent="#accordian" href="#collapse-{{ event_name.grouper|slugify }}">
 {{ event_name.grouper|title }}
</a>
</h5>
</div>
</div>
<div id="collapse-{{ event_name.grouper|slugify }}" class="panel-collapse collapse in">
<div class="panel-body">
    {% for item in event_name.list %}
 # continue iterating the items in the list

This is the render method from the React component:

  render() {
  const { error, isLoaded, items, groups } = this.state;
    if (error) {
      return <div>Error: {error.message}</div>;
    } else if (!isLoaded) {
      return <div>Loading...</div>;
    } else {
    return (
        <div>
        {items.map(item => (
            <h4 key={item.id}>{item.event}</h4>
            )
        )}

        </div>
        );
        }
  }
}
Matts
  • 1,301
  • 11
  • 30
  • I would recommend doing the groupby in the client, since you don't want to implement presentation logic in you api. It's not very complicated to implement a groupby function in javascript. See this question, for example. https://stackoverflow.com/q/14446511/1977847 – Håken Lid Mar 18 '18 at 14:05
  • Thanks, I'll try out some of the functions from the example. – Matts Mar 18 '18 at 14:12

2 Answers2

3

If you are setting up a React front end, it would be more sensible not to mix django templates with the react front end components. Instead set up a Django/DRF backend api that will feed your react components with JSON data.

AR7
  • 366
  • 1
  • 16
  • I had originally set up the templates, in Django, but I now decided I want to use React front end instead. The Django templates will be removed. DRF is feeding the React components with JSON data. – Matts Mar 18 '18 at 14:16
1

To translate this template from django to react, you just have to reimplement the regroup template tag as a javascript function. For pretty much any django template tag, you can easily find some javascript library that does the same. This is not included in React.js, but instead you can import utilities from libraries such as underscore or moment.js etc.

This is the sample django template code from the example in the documentation for the {% regroup %} template tag.

{% regroup cities by country as country_list %}

<ul>
{% for country in country_list %}
    <li>{{ country.grouper }}
    <ul>
        {% for city in country.list %}
          <li>{{ city.name }}: {{ city.population }}</li>
        {% endfor %}
    </ul>
    </li>
{% endfor %}
</ul>

Here's how you could do it with react.js

// React ( or javascript ) doesn't come with a groupby function built in.
// But we can write our own. You'll also find this kind of stuff in lots 
// of javascript toolsets, such as lowdash, Ramda.js etc.

const groupBy = (key, data) => {
  const groups = {}
  data.forEach(entry => {
    const {[key]: groupkey, ...props} = entry
    const group = groups[groupkey] = groups[groupkey] || []
    group.push(props)
  })
  return groups
}
// I'll define a dumb component function for each nested level of the list.
// You can also write a big render function with everything included, 
// but I find this much more readable – and reusable.

const City = ({ name, population }) => (
  <li> {name}: {population} </li>
)

const Country = ({ country, cities }) => (
  <li>
    {country}
    <ul>{cities.map(props => <City key={props.name} {...props} />)}</ul>
  </li>
)

const CityList = ({ cities }) => {
  const groups = Object.entries(groupBy("country", cities))
  return (
    <ul>
      {groups.map(([country, cities]) => (
        <Country key={country} country={country} cities={cities} />
      ))}
    </ul>
  )
}

// We'll use the exact same data from the django docs example.

const data = [
  { name: "Mumbai", population: "19,000,000", country: "India" },
  { name: "Calcutta", population: "15,000,000", country: "India" },
  { name: "New York", population: "20,000,000", country: "USA" },
  { name: "Chicago", population: "7,000,000", country: "USA" },
  { name: "Tokyo", population: "33,000,000", country: "Japan" }
]

ReactDOM.render(<CityList cities={data} />, document.getElementById("app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<main id=app></main>

If you run the snippet above, you should get the exact same output as what you find in the original django example.

<ul>
  <li>India
    <ul>
      <li>Mumbai: 19,000,000</li>
      <li>Calcutta: 15,000,000</li>
    </ul>
  </li>
  <li>USA
    <ul>
      <li>New York: 20,000,000</li>
      <li>Chicago: 7,000,000</li>
    </ul>
  </li>
  <li>Japan
    <ul>
      <li>Tokyo: 33,000,000</li>
    </ul>
  </li>
</ul>
Håken Lid
  • 22,318
  • 9
  • 52
  • 67
  • Thanks for your answer. I was looking to have something more concise and reusable like the Django regroup method. I'm trying the lowdash method, but haven't been able to get the desired output yet. I edited my question. – Matts Mar 19 '18 at 05:39
  • I almost have it working using your method, but it's repeating the 2nd level too many times. To make the 2nd level I duplicated the Country element. – Matts Mar 19 '18 at 07:32
  • For nested groups, you either have to call the groupBy function for each sub group, or you can check out `d3.nest` from the `d3-collection` module. https://github.com/d3/d3-collection#nests – Håken Lid Mar 19 '18 at 08:48
  • I'm not sure where to call the groupBy function for each sub group. I tried in a number of places, but can't quite get it to work. – Matts Mar 19 '18 at 09:03
  • Transform the flat data into the nested shape you want in the container componentet, and pass the nested data down to child components as props. To build on the example in my answer, let's say the data contained years, and you want to nest countries inside years. Then you could add a `Year` component similar to `Country`, and in the container, use `const groups = d3.nest().key(d => d.year).key(d => d.country).entries(data)` to reshape the input data. – Håken Lid Mar 19 '18 at 09:15
  • If you could edit your answer to show how it works I'd greatly appreciate it. Thanks for all your help with this. – Matts Mar 19 '18 at 14:43