Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multistep Wizard example - How to access this.state.value from Wizard inside each Wizard.page? #844

Closed
Huanzhang89 opened this issue Aug 24, 2018 · 23 comments

Comments

@Huanzhang89
Copy link
Contributor

I am using the multistep wizard from the examples section to implement a 2 page form but there is one small issue that I have and haven't been able to figure out.

The example showed how to easily hook up the input fields so that changes update automatically in the Wizard component's state.

For example I have a Field like the one below and I want the user's selection in this Field to affect the label of a sibling input. I am struggling to find a way to access the value of the Colour field.

<div className="form-input">
      <label className="input-label">Select Colour</label>
      <Field name="colour" component="select">
        <option value={"red"}>Red</option>
        <option value={"blue"}>Blue</option>
        <option value={"yellow"}>Yellow</option>
      </Field>
    </div>

I have successfully attached state of the Wizard component to each child component using the following code:

const childrenWithState = React.Children.map(children, child => {
    return React.cloneElement(child, { parentState: this.state })
})
const activePage = React.Children.toArray(childrenWithState)[page]

But what I am stuck on is how to access the props in Wizard.Page, since we are just calling the static method Page on the Wizard component and have no access to this.

@njj
Copy link

njj commented Aug 25, 2018

@Huanzhang89 Have you considered abstracting the Page into its own component and then passing state from the parent as props, using a callback for updating props to the parent, etc..?

@Huanzhang89
Copy link
Contributor Author

Huanzhang89 commented Aug 27, 2018

@njj I did consider that but the issue here is passing the props down to Wizard.Page and accessing them inside Wizard.Page. Even if we abstract the Page into its own component, at the point where we call <Page props={parentState} /> we still do not have access to the parentState to pass into the Page component.

I have however found a solution to this using renderProps! The problem is that inside the static method Wizard.Page, the props are not passed down to the children as it simply returns the children.

static Page = ({ children }) => {
    return children
  }

By modifying this static method to

static Page = ({ children, parentState }) => {
    return children(parentState)
  }

And also modifying the JSX inside each Wizard.Page to be a function that returns the JSX, we can pass the parentState down to each Wizard.Page, like so.

  <Wizard.Page>
              {props => {
                return (
                     // JSX here
                )}
               }
  </Wizard.Page>

props in the above function = parentState

Hope this helps anyone else getting stuck with this issue!

@njj
Copy link

njj commented Aug 27, 2018

@Huanzhang89 Nice, you could also do this by using React.cloneElement for the {activePage}, i.e:

{ React.cloneElement(activePage, { ...props }) 

@Huanzhang89
Copy link
Contributor Author

Oh really, do you mean instead of just {activePage} inside the render function of the Wizard?

But if you look at the first post I made, I am already using React.cloneElement to pass the parentState to the activePage.

I think we still need to renderProps inside Wizard.Page to pass the parentState down to the children. Not 100% sure about this though.

@njj
Copy link

njj commented Aug 27, 2018

@Huanzhang89 Right then you will need to handle it in the Page function again. Either way I think works. I ended up doing something similar, but I'm passing the Formik props (values, fns, etc..) so my fields can have them.

@Huanzhang89
Copy link
Contributor Author

Ah ok I see what you mean. Maybe we should create a PR to either update the current example or create a new multipage example? I can see a lot of use cases where we would need to access the form data inside each page and its not immediately clear how this can be done from the current example.

@Huanzhang89
Copy link
Contributor Author

@njj Actually after some testing I found that my method doesn't update the form values correctly as the React.cloneElement was outside of the Formik component. So your method of { React.cloneElement(activePage, { ...props }) is the way to go!

@shoaibkhan94
Copy link

Hi @Huanzhang89 @njj I am stuck on the same issue. Can you please share a code snippet or example to fix it. Any help would be really appreciate!
Thanks

@longnt80
Copy link

longnt80 commented Oct 9, 2018

@shoaibkhan94 here's the code example with their solution:
https://codesandbox.io/s/62nk7x0p73

@shoaibkhan94
Copy link

@longnt80 Thanks. It's working!

@Huanzhang89
Copy link
Contributor Author

Sorry guys been away for a while, thanks @longnt80 for working it out and helping :)

@timrombergjakobsson
Copy link

Hey I saw this issue. Im having a somewhat similar problem. Im trying to access the values from a radiobutton and use that as an conditional rendering of a wizard.page. For example Im wrapping pages like this:

<Wizard.Page validate={validators.validateFirstPage}>
                { props => (
                        <React.Fragment>
                        ....more code here

And I have 3 radiobuttons on the first page and I would like to use the value of the radiobuttons to conditionally render pages like:

 { props => (                 
                    <React.Fragment>
                         { props.values.radioGroup1 === 'daily' ? (
                          .... render content here

But it is not working. Im getting a TypeError: children is not a function from my wizard class right here:

static Page = ({ children, parentState }) => {
      return children(parentState); <---- from here.
    };

I must be doing something wrong when trying to render base on the value of the radioGroup. Anyone tried anything like this?

@Huanzhang89
Copy link
Contributor Author

@timrombergjakobsson

Hey Tim, can you log out what children is inside static Page ? Could be you cloned the activepage wrong and it Wizard.Page does not have access to this.props.children?

Can you also page your React.cloneElement part of the form as that would shed some light on the issue!

@timrombergjakobsson
Copy link

@Huanzhang89 heres the React.cloneElement part:

render() {
        const { handleSubmit, children, prevButton, nextButton, classes } = this.props;
        const { page, values } = this.state;
        const activePage = React.Children.toArray(children)[page];
        const isLastPage = page === React.Children.count(children) - 1;
        const isFirstPage = page === 0;

        const PrevButton = this.props.prevButton;
        const NextButton = this.props.nextButton;
        let currentStep = this.state.page;
        console.log(values);
        console.log(this.props.children, 'some children');
        return (
            <section className="site-main__content-wrapper">
              <Header currentStep={currentStep}/>
              <article className="site-main__content content-padding">
                 <div className="reminder__start">
                    <Formik
                      initialValues={this.state.values}
                      validate={this.validate}
                      enableReinitialize={false}
                      onSubmit={this.handleSubmit}
                      { ...children }
                      render={props => ( 
                        <form className="reminder-form" onSubmit={props.handleSubmit}>
                            {React.cloneElement(activePage, { parentState: { ...props } })}

@Huanzhang89
Copy link
Contributor Author

Huanzhang89 commented Nov 28, 2018

Okay so one thing I notice is that you are spreading children and assigning it to the props of Formik. You should remove that.

However the true culprit for your error is actually the fact that you spread props inside parentState. This means that your Page no longer has access to this.props.children.

{React.cloneElement(activePage, { props.children, parentState: { ...props } })} would work or
{React.cloneElement(activePage, { parentState: props.parentState })} assuming thats what you wanted to pass to parentState

@timrombergjakobsson
Copy link

timrombergjakobsson commented Nov 28, 2018

@Huanzhang89 ah ok, so remove children from here: const { handleSubmit, children, prevButton, nextButton, classes } = this.props; ? What about this part { ...children }? And is it not supposed to be {React.cloneElement(activePage, { props: props.children, parentState: { ...props } })} and not {React.cloneElement(activePage, { props.children, parentState: { ...props } })}?

@Huanzhang89
Copy link
Contributor Author

Yes you should remove { ...children } since that prop is not being used by formik.

For the second point I'll try to explain. The entire object of the second argument for React.cloneElement gets assigned to the props of the resulting React element. Remember the props of a React Element is simply an object, so in your case the props of your Element would be { props.children: Function, parentState: Object }. And to access the children you would have to use this.props.props.children which doesn't really make sense.

Did making that change fix your issue btw?

@timrombergjakobsson
Copy link

@Huanzhang89 I think Im with you, the only thing is that {React.cloneElement(activePage, { props.children, parentState: { ...props } })}does not work, cos its syntax error. So I had to change to {React.cloneElement(activePage, { props: props.children, parentState: { ...props } })} instead.

@Huanzhang89
Copy link
Contributor Author

Ah sorry I made a mistake with that, it should be {React.cloneElement(activePage, { children: ...props.children, parentState: { ...props } })

But I would actually recommend not spreading ...props into parentState. Rather construct the parentState object before passing it to cloneElement here.

@timrombergjakobsson
Copy link

Ah alright, ok great thanks. Well yes I had to do a quite verbose and ugly solution to my problem:

<Wizard.Page validate={validators.validateSecondPage}>
                { props => (                 
                    <React.Fragment>
                        { props.values.radioGroup1 === 'daily' ? ( <--- this is the condtional.
                        html here

Rather construct the parentState object before passing it to cloneElement

How do you mean?

@stale stale bot added the stale label Jan 27, 2019
@mjangir
Copy link

mjangir commented Mar 23, 2019

You can check out this wizard component built for formik:

https://github.com/mjangir/formik-wizard-form

@stale stale bot removed the stale label Mar 23, 2019
@stale stale bot added the stale label May 22, 2019
@stale stale bot removed the stale label May 22, 2019
@Andreyco
Copy link
Collaborator

closing in favor of #1315

@vincentntang
Copy link

I wrote an example using Reactstrap (UI library) with Yup + Formik, demonstrating all the important features for a wizard:

https://github.com/vincentntang/multistep-wizard-formik-yup-reactstrap

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants