0

I have the following page in React:

@connect((store) => {
  return {
    all_orders_data: store.trends.all_orders_data,
    ...etc...
  };
})
class OrdersController extends MyComponent {

    constructor(props){
        let custom_methods = ['refreshData', 'runDispatch'];
        super(props, custom_methods);
        this.state = {last_updated_at: new Date()};
    }

    componentWillMount() {
        this.runDispatch();
        // this.props.dispatch(fetchOrderData());
    }

    componentDidMount() {
        this.refreshTimerID = setInterval(
            () => this.refreshData(),
            300 * 1000 // 5 minutes
        );
    }

    refreshData() {
        console.log('running refresh');
        this.runDispatch(true);
        this.setState({last_updated_at: new Date()});

    }

    runDispatch(refresh=false) {
        this.props.dispatch(fetchOrderTrendingAllOrdersData(refresh));
    }

    render() {
        return (
            <div>
                <h1>Order Trending</h1>
                {last_updated_at}
                <br/>
                <div className="card col-md-12">
                    <h2 className="style-1">All Orders</h2>
                    <LineChart
                        data={all_orders_data}
                        fetched={fetched_all_orders_data}
                        error={error_in_fetching_all_orders_data}
                    />
                </div>
            </div>
        )
    }

In the render I unpack the props, the last_updated header, and render a line chart.

enter image description here

I want to be able to click buttons to toggle lines on the chart. To do this, I have to keep track of the keys for the data variable to show as lines.

I can't put these line options in the constructor because the runDispatch isn't fired off until componentWillMount.

I can't put it in componentWillMount because I don't know when runDispatch will return for data until it does return.

I can get the keys for the all_orders_data in the reducer and pass it to OrdersController as a prop, but props can't be changed and I want this page of our app to control the current lines showing.

I don't want to put it on the chart component because it get's new props every time to refresh runs, and I don't know if it will maintain the proper state after refresh.

The toggle setting doesn't need to be retained later, only while the controller is active (if they pick a new link I don't care if it resets).

My gut is to put state on the line chart since it doesn't have to be permanent.

Where is the correct place to keep the state of these line keys, like:

{
  all_orders: ['Online orders', 'Web orders', ...]
}

to let the user toggle what lines he wants to see on graph? It can be on the LineChart, the controller, a new redux prop, etc.

PEHLAJ
  • 9,980
  • 9
  • 41
  • 53
codyc4321
  • 9,014
  • 22
  • 92
  • 165

1 Answers1

0

I decided to put it on the LineChart itself, since the buttons toggle lines on it. It wasn't working, but you can protect the state in componentWillReceiveProps:

import React from 'react';
import ReactLoading from 'react-loading';
import {
    VictoryChart,
    VictoryLine,
    VictoryTheme,
    VictoryAxis,
    VictoryTooltip,
    VictoryBar,
    VictoryVoronoiContainer
} from 'victory';
import _ from 'underscore';

import MyComponent from '../main/MyComponent';


const COLOR_OPTIONS = [
    '#c43a31', // dark red
    'blue',
    'green',
    'yellow',
    'purple',
    'teal',
    'orange'
];

function getTimestringFromUnixTimestamp(timestamp) {
    // just default it to AM for now
    let period = 'AM'
    let date = new Date(timestamp);
    let hours = date.getHours();
    let minutes = date.getMinutes();

     if (hours >= 12) {
        period = 'PM';
    }

    if (hours == 0) {
        hours += 12;
    } else if (hours >= 13) {
        hours -= 12;
    }

    hours = "0" + hours;
    minutes = "0" + minutes;
    // Will display time in 10:30 AM format
    let formattedTime = `${hours.substr(-2)}:${minutes.substr(-2)} ${period}`;
    return formattedTime
}

function displayTooltips(data) {
    // x is the unix timestamp, y is the order count
    let { x, y } = data;
    let formattedTime = getTimestringFromUnixTimestamp(x);
    // https://stackoverflow.com/questions/847185/convert-a-unix-timestamp-to-time-in-javascript
    return `Time - ${formattedTime}\nOrder Count - ${y}`
}




export default class LineChart extends MyComponent {

    constructor(props) {
        let custom_methods = [
            'generateVictoryLines',
            'generateToggleButtons',
            'toggleItemOnclick',
            'toggleAllLines',
        ];
        super(props, custom_methods);

        this.state = {
            active_line_keys: [],
            available_line_keys: []
        };
    }

    componentWillReceiveProps(nextProps) {
        console.log('\n\ncomponentWillReceiveProps:');
        let data = nextProps.data;
        console.log(data);
        if (data) {
            let line_keys = Object.keys(data);
            console.log('line_keys:');
            console.log(line_keys);
            console.log('this.state.available_line_keys:');
            console.log( this.state.available_line_keys);
            let is_equal = _.isEqual(_.sortBy(line_keys), _.sortBy(this.state.available_line_keys));
            if (!is_equal) {
                console.log('line keys are diff; need to update state');
                this.setState({
                    available_line_keys: line_keys,
                    active_line_keys: line_keys
                });
            }
        }
    }

    generateVictoryLines() {
        return this.state.active_line_keys.map((key, index) => {
            let this_keys_permanent_index = this.state.available_line_keys.indexOf(key);
            let color = COLOR_OPTIONS[this_keys_permanent_index];
            return (
                <VictoryLine
                  labels={displayTooltips}
                  labelComponent={<VictoryTooltip/>}
                  style={{
                    data: { stroke: `${color}` },
                    parent: { border: `1px solid #ccc`}
                  }}
                  data={this.props.data[key]}
                />
            )
        });
    }

    generateToggleButtons() {
        return this.state.available_line_keys.map((key, index) => {
            let this_keys_permanent_index = this.state.available_line_keys.indexOf(key);
            let color = COLOR_OPTIONS[this_keys_permanent_index];
            console.log(key);
            return (
                <button onClick={this.toggleItemOnclick.bind(null, key)} style={ {color: color}}>{key}</button>
            );
        })
    }

    toggleItemOnclick(name) {
        console.log('\ntoggleItemOnclick:');
        console.log(name);
        console.log(this.state);
        let is_in_active_line_keys = this.state.active_line_keys.indexOf(name) != -1;
        console.log(is_in_active_line_keys);

        let new_active_line_keys;
        if (is_in_active_line_keys) {
            new_active_line_keys = this.state.active_line_keys.filter(e => e !== name); // e is each item in the list; filter
            // this.setState({active_line_keys: new_active_line_keys});
        } else {
            new_active_line_keys = this.state.active_line_keys.slice();
            new_active_line_keys.push(name);
        }

        console.log(new_active_line_keys);

        this.setState({active_line_keys: new_active_line_keys});
        // arr = arr.filter(e => e !== el);
    }

    toggleAllLines() {
        if (this.state.active_line_keys.length < this.state.available_line_keys.length) {
            this.setState({active_line_keys: this.state.available_line_keys});
        } else {
            this.setState({active_line_keys: []});
        }
    }

    // addAllLines() {
    //     this.setState({active_line_keys: this.state.available_line_keys});
    // }

    render() {
        let graph_body;
        let { data, error, fetched } = this.props; // we don't need data here
        let victory_lines = this.generateVictoryLines();
        let buttons = this.generateToggleButtons();

        if (error) {
            // alert(error);
            console.log('\n\nerror:');
            console.log(error);
            graph_body = (<h2 className="centered">Error retrieving data</h2>);
        } else if (fetched == false) {
           graph_body = (
               <div className="card col-md-12">
                   <span><h2 style={{fontWeight: "bold", fontSize: "25px", color: "#F96302"}}>Just a moment...Your request is being processed.</h2>
                   <ReactLoading type="cylon" color="#F96302" /></span>
               </div>
           )
        } else {
            try {

              // in the victoryLine, interpolation="natural" will smooth graph out

               graph_body = (
                   <div>
                      <VictoryChart
                        theme={VictoryTheme.material}
                        scale={{x: "time", y: "linear"}}
                        animate={{duration: 650}}
                        containerComponent={<VictoryVoronoiContainer/>}
                        width={1500}
                        height={600}
                      >
                          <VictoryAxis style={ { tickLabels: {fontSize: '20px'} } }/>
                          <VictoryAxis
                            dependentAxis
                            tickValues={[20, 40, 60, 80, 100]}
                            style={ { tickLabels: {fontSize: '20px'} } }
                          />
                          {victory_lines}

                      </VictoryChart>
                      <button onClick={this.toggleAllLines}>Toggle lines</button>
                      {buttons}

                   </div>
               )
           } catch(err) {
               graph_body = (<h2 className="centered">Error using response: {`${err}`}</h2>);
           }
        }

        return (
            <div>
                {graph_body}
            </div>
        )
    }

}
codyc4321
  • 9,014
  • 22
  • 92
  • 165