0

I have a react component, that uses material-ui tabs and react-swipeable-views. The problem I am experiencing is that, when switching tabs, the url displayed does not change, I noticed that is the react-swipeable-views logic. I would like to apply react-router as well in order to change the urls on tab change while preserving the react-swipeable-view logic.

Here is a part of my code below with some additional details:

import SwipeableViews from 'react-swipeable-views';
import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';

export default function TasksViewTabs(props) {
        const task = props.match.params.taskid
        const classes = useStyles();
        const [value, setValue] = React.useState(0);
    
        const handleChange = (event, newValue) => {
            console.log('new value', newValue)
            setValue(newValue);
        };
    
        const handleChangeIndex = (index) => {
            setValue(index);
        };
    
        console.log('value', value)
    
        return (
            <>
                    <Tabs value={value} onChange={handleChange} classes={{indicator: classes.indicator}}>
                        <Tab className={value === 0? classes.active_tabStyle : null} label="Runs" {...a11yProps(0)} />
                        <Tab className={value === 1? classes.active_tabStyle : null} label="Tasks" {...a11yProps(1)} />
                        <Tab className={value === 2? classes.active_tabStyle : null} label="Settings" {...a11yProps(2)} />
                    </Tabs>
                <SwipeableViews index={value} onChangeIndex={handleChangeIndex}>
                        <RunApp value={value} index={0} taskid={task} />
                        <TaskApp value={value} index={1} taskid={task} />
                        <SettingsApp value={value} index={2} taskid={task} />
                </SwipeableViews>
            </>
        );
    }

The default url displaying looks like this, it doesn't change when view is changed : localhost:3000/clusters/<task:id> But I have three tabs (runs, tasks, settings), which control the react-swipeable-views. I want the url to display like (localhost:3000/clusters/<task:id>/runs, localhost:3000/clusters/<task:id>/tasks, localhost:3000/clusters/<task:id>/settings) respectively on tab change.

How can I achieve that and preserve the react-swipeable logic?

Thanks

Just incase, the url is loaded from a history.push from a component that contains a table. only on clicking a table row.

  rowClicked = (params) => {
    return (
      history.push({
        pathname: `/clusters/${params.data.md5_hash}`
      })
    )
  }

Thanks, I hope that this helps :)

Derrick Omanwa
  • 457
  • 1
  • 5
  • 23
  • Presumably all three tab components are rendered on the same sub-route. Can you include your Router/routing component and code? – Drew Reese Nov 03 '20 at 07:28
  • Hello there, I am not really using react routers to render the tab components, It's just a `history.push()` that leads to the page that contains the tabs. Then the change of tabs is controlled by `react-swipeable-views`, no react-router is needed. – Derrick Omanwa Nov 03 '20 at 07:34
  • Right, but *what* are you pushing to? You will need to "push" to each sub-route if you want the URL to change (without just mutating the history object). – Drew Reese Nov 03 '20 at 07:35
  • Okay, now I get you, let me add the history.push() code that leads to the tabs page – Derrick Omanwa Nov 03 '20 at 07:37
  • Just edited. Thanks – Derrick Omanwa Nov 03 '20 at 07:43

2 Answers2

1

I figured out the answer to this after some research. The best library to react swipeable library for route change is react-swipeable-routes, so I changed to this from react-swipeable-views and applied the use of react routes and it worked like a charm.

//import SwipeableViews from 'react-swipeable-views'; //don't use this
import SwipeableRoutes from "react-swipeable-routes"; //use this
import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';
export default function FullWidthTabs(props) {
    const task = props.match.params.taskid
    // const theme = useTheme();
    const classes = useStyles();
    const [value, setValue] = React.useState(0);

    const handleChange = (event, newValue) => {
        console.log('new value', newValue)
        setValue(newValue);
    };

    const handleChangeIndex = (index) => {
        setValue(index);
    };

    console.log('value', value)

    return (
        <>
            <HeaderContent>
                <Breadcrumbs />
                <ProfileMenu/>
            </HeaderContent>
            <Router> //use react-router
            <ToolBarContent>
//add routes in tabs
                <Tabs value={value} onChange={handleChange} classes={{indicator: classes.indicator}} style={{display: 'flex', justifyContent: 'space-between'}}>
                    <Tab to= {`clusters/${task}/runs`} className={value === 0? classes.active_tabStyle : null} label="Runs" {...a11yProps(0)}/>
                    <Tab to= {`clusters/${task}/tasks`} className={value === 1? classes.active_tabStyle : null} label="Tasks" {...a11yProps(1)}/>
                    <Tab to= {`clusters/${task}/settings`} className={value === 2? classes.active_tabStyle : null} label="Settings" {...a11yProps(2)}/>
                </Tabs>
            </ToolBarContent>
//the magic happens here
            <SwipeableRoutes  index={value} onChangeIndex={handleChangeIndex}>
            <Route path={`clusters/${task}/runs`} component={() => <RunApp value={value} index={0} taskid={task} />}/>
            <Route path={`clusters/${task}/tasks`} component={() => <TaskApp value={value} index={0} taskid={task} />}/>
            <Route path={`clusters/${task}/settings`} component={() => <SettingsApp value={value} index={0} taskid={task} />}/>
          </SwipeableRoutes>
          </Router>
        </>
    );
}
Derrick Omanwa
  • 457
  • 1
  • 5
  • 23
0

Assuming you've a Route to match path "/clusters/:taskId"

<Route path="/clusters/:taskId" component={TasksViewTabs} />

Then this route can match any route with "/clusters/:taskId" as a prefix, i.e.

  • "/clusters/:taskId/runs"
  • "/clusters/:taskId/tasks"
  • "/clusters/:taskId/settings"

You can update your handleChangeIndex to also imperatively navigate to the matching subroute when the index changes.

Create a tab/route config

const tabConfig = [
  {
    component: RunApp,
    label: 'Runs',
    link: 'runs',
  },
  {
    component: TaskApp,
    label: 'Tasks',
    link: 'tasks',
  },
  {
    component: SettingsApp,
    label: 'Settings',
    link: 'settings',
  },
];

Update the render to map the tabConfig to the tabs

return (
  <>
    <Tabs
      value={value}
      onChange={handleChange}
      classes={{ indicator: classes.indicator }}
    >
      {tabConfig.map(({ label }, i) => (
        <Tab
          key={i}
          className={value === i ? classes.active_tabStyle : null}
          label={label}
          { ...a11yProps(i) }
        />
      ))}
    </Tabs>
    <SwipeableViews
      index={value}
      onChangeIndex={handleChangeIndex}
    >
      {tabConfig.map(({ component: Component}, i) => (
        <Component key={i} value={value} index={i} taskid={task} />
      ))}
    </SwipeableViews>
  </>
);

Update handleChangeIndex to do some navigating.

const { history, match } = props;
const { params: { taskid: task } } = match;

...

const handleChangeIndex = (index) => {
  setValue(index);
  history.replace(`/clusters/:taskId/${tabConfig[index].link}`);
};
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • hey Drew, thanks for your answer. But for some reason it didn't work for me. I really appreciate it though. – Derrick Omanwa Nov 03 '20 at 12:37
  • 1
    @DerrickOmanwa Yeah, I'm sure my solution is close (without having a way to test other than completely setting up a project) and may only need some tweaking. I see a note at the bottom of the [react-swipeable-routes](https://www.npmjs.com/package/react-swipeable-routes) that "Unlike react-router, with react-swipeable-routes all routes have to be rendered at all times." I don't totally agree with this statement though since routes ***not*** in a `Switch` can have multiple matches. This is why there needs to be a single base route to match the prefix. Cheers. – Drew Reese Nov 03 '20 at 16:27