1

I want to create a wizard that changes content when the user clicks a "next" button.

I'm currently trying to use the .map function, it works but how can I adjust my code to loop over each step in my array onClick?

Currently, my code just displays 3 separate inputs with all of the steps in the array, what I want to do is iterate over each step onClick.

Here is an example of my code:

Array:

const wizardControls = {
    steps: [
        {
            step: 1,
            name: 'name1',
            type: 'text',
            placeholder: 'name1',
        },
        {
            step: 2,
            name: 'name2',
            type: 'text',
            placeholder: 'name2',
        },
        {
            step: 3,
            name: 'name3',
            type: 'text',
            placeholder: 'name3',
        },
    ],
};

JSX using map() function:

    {steps.map((step, index) => (
        <div key={index}>
            <input
                value={value}
                name={step.name}
                type={step.type}
                placeholder={step.placeholder}
                onChange={onChange}
            />
        </div>
    ))}

I'm thinking the button will need a handler function to loop over the index, however, I'm unsure how to do this with the map() function.

I'm open to a better approach if the map() function isn't the best route.

2
  • 1
    If I understand correctly you want to start off showing index 0 only then when the user hits next show index 1 only, so on and so forth? This could probably be done with a useState to hold the current index to display and then without a map just use the index to render the data you want. When you click next just increment the index stored in the useState. You might need a bit of validation on the selected index depending on what else you intend to do with it to ensure that the array actually contains the index that is requested. Commented Jan 21, 2021 at 22:18
  • Yes, this is exactly it. What I forgot to mention in my question is that the above array is already stored using useState. Was just having trouble understanding how to use map() and iterate onClick. - I appreciate your insight! Commented Jan 21, 2021 at 22:33

2 Answers 2

2

One way you could do this is by slicing by which step you're on (based on index).

Here's an example of what that might look like with your code.

const [step, setStep] = useState(1)

...

steps.slice(step - 1, step).map((step, index) => (
  ...
))

See a working example here: https://codesandbox.io/s/pensive-northcutt-el9w6

Sign up to request clarification or add additional context in comments.

1 Comment

This is an interesting alternative. Thank you for taking the time to answer!
1

If you want to show a step at a time, don't use Array.map() to render all of them. Use useState to hold the current index (step), and take the current item from the steps array by the index. To jump to the next step, increment the index by 1.

const { useState } = React;

const Demo = ({ steps }) => {
  const [index, setIndex] = useState(0);
  const [values, setValue] = useState([]);
  
  const next = () => 
    setIndex(step => step < steps.length -1 ? step + 1 : step);
  
  const onChange = e => {
    const val = e.target.value;
    
    setValue(v => {
      const state = [...v];
      
      state[index] = val;
      
      return state;
    })
  };
  
  const step = steps[index];

  return (
    <div>
        <input
            value={values[index] || ''}
            name={step.name}
            type={step.type}
            placeholder={step.placeholder}
            onChange={onChange}
        />
        
        <button onClick={next}>Next</button>
    </div>
  );
};

const wizardControls = {"steps":[{"step":1,"name":"name1","type":"text","placeholder":"name1"},{"step":2,"name":"name2","type":"text","placeholder":"name2"},{"step":3,"name":"name3","type":"text","placeholder":"name3"}]};

ReactDOM.render(
  <Demo steps={wizardControls.steps} />,
  root
);
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>

<div id="root"></div>

5 Comments

This is what I am attempting. - If you don't mind, can you add some notes for useCallback? Until now, I have not used that hook and need to wrap my head around it if I move forward with your solution. - I've already looked up the documentation but still want to understand how you've used it in the context of this functionality. - Thank you in advance!
The useCallback is not strictly necessary here. The main idea is to preserve the function when the component is re-rendered, so the children won't be re-rendered as well due to the changing property (the function). It's a performance optimization, that can be skipped here.
Thanks. What would this look like without useCallback? In the snippet, it seems like it's used twice. - Not that I want this to be less optimized but if I were to do this with only useState would I simply remove useCallback from the code altogether? Sorry for the additional questions, I just want to understand this in case I have to fully refactor my app state.
The optimization is not really necessary here, just a habit from my usual project. I'll remove it, since it's distracting from the actual solution :)
I really appreciate it. I'll likely refer back to the way you utilized useCallback in your original answer on a future date.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.