Selecting React Components

Consider a React App with a navigation menu.
The menu should create buttons for each ‘view’. Consider an array of views, where each view is a component :
[<view1 />, <view2 />, <view3 />]
The menu maps each item in the array to a button component, passing down the view item, and a click handler, like so:
views.map(item =>({<ButtonComponent item={item} handler={handler}/>}))
In the button component, the onClick property is set to the handler we passed down. The value is set to the view item we passed down.
So basically, the button has a view assigned to it, that it then passes back up to the handler, which sets it as state and renders it and so on. The handler, in the console logs, just reads the value as ‘[object Object]’.
I was able to achieve this with a switch statement and some text in the handler, but all of that was hard coded, and I want this to be more abstract so I can easily add new views, etc. Can you just pass components around as props or am I going about this wrong? What’s the best way to implement this sort of thing in a way that’s more scalable?

You put it in state. You don’t join the component to the button, that’s not what React pushes you to do (and this doesn’t work in just HTML or similar, that’s not how you structure things). There are multiple ways to do this (React Router solves this immediately for example). Simplest is probably:

In the parent, have state that holds the name of the currently selected “view”. Then add your buttons + alongside them add the same number of another component <View/>.

Give each button and each view a “name” prop. Pass the buttons the state + a function that updates the state to a different name. Pass the view components the state.

  • If the name of the button === the name of the currently selected view, that button is selected (style accordingly)
  • If the button is clicked, set the name of the currently selected view to whatever the name props is.
  • If the name of the view === the name of the currently selected state, render it’s children (your actual content). Otherwise return null (don’t render).
function ViewSwitcher () {
  const [viewName, setViewName] = React.useState("foo");

  return (
    <>
      <ViewSwitcherButton name="foo" currentViewName={viewName} setViewName={setViewName} />
      <ViewSwitcherButton name="bar" currentViewName={viewName} setViewName={setViewName} />
      <ViewSwitcherButton name="baz" currentViewName={viewName} setViewName={setViewName} />

      <View name="foo" currentViewName={viewName}>
        // your stuff here
      </View>
      <View name="bar" currentViewName={viewName}>
        // your stuff here
      </View>
      <View name="baz" currentViewName={viewName}>
        // your stuff here
      </View>
    </>
  );
}

function ViewSwitcherButton ({ name, currentViewName, setViewName }) {
  // if name === currentViewName, button is selected
  return <button onClick={() => setViewName(name)>{name}</button>;
}

function View ({children, name, currentViewName}) {
  if (name === currentViewName) {
    return <>{children}</>;
  } else {
    return null;
  }
}

You can map the buttons and/or views or whatever.

Note this is an ideal usecase for Context, which removes the need to manually pass props, you can just access the state anywhere directly. It would be preferable to use it here, it adds a tiny bit extra setup code but then the actual implementation becomes simpler and more flexible.

I think the children inside view is where I’m getting stuck with this sort of setup. I can basically get this logic working, in that it’ll update state and render the correct view, but I’m not understanding how to tie that now abstract view element to content. Consider, for example, 2 predefined views:

<Dashboard /> <Contacts />

I have each of those views as their own, complete standalone components. With my current setup, I can switch between them with a switch statement, but in this setup, where does ‘children’ come into play? What would theoretically export that?

Children is a prop that’s available implicitly on every react component, it is just the child component/s (props.children on function components or this.props.children on class components). It is particularly useful when you want a reusable wrapper component.

function ChildrenExample ({ children }) {
  <div>{children}</div>
}

It’s anything, any React component/s

<ChildrenExample>
  <h1>Put anything</h2>
  <p>you want</p>
  <p>in here.</p>
</ChildrenExample>
<ChildrenExample>
  <Dashboard />
</ChildrenExample>

<ChildrenExample>
  <Contacts />
</ChildrenExample>

Got it! My code works, AND is nice to look at now! Thanks for taking your time to help me, I appreciate it :smiley:

1 Like