0

TLDR

I'm making an multiStep form for my project that is inspired from Brad Traversy's Tutorial of making Multi-Step form in React. So as per the basic structure of this form enter image description here
I made a main Parent component called Multiform as below

import React, { Component } from 'react'
import StepOne from './StepOne'
export class Multiform extends Component {
    state = {
    step:1,
    CountryFlag: 'https://raw.githubusercontent.com/MeRahulAhire/country-calling-code-html/master/phone_icon.png',
    CountryCode: ''
};
handleChange = (input) => (e) => {
    this.setState({ [input]: e.target.value });
};

countryFlagHandler = () =>{
    this.setState({CountryFlag : this.props.state.flagImg})
  }
render() {
    const { CountryFlag, CountryCode } = this.state;
    const values = {CountryFlag, CountryCode };

    switch (step) {
        case 1:
          return(
            <StepOne
            handleChange={this.handleChange}
            countryFlagHandler={this.countryFlagHandler}
            values={values}
            />

          )
         default:
             return (<h1>hello React App</h1>)

    };
}
}

export default Multiform

and a child component StepOne as below

import React, { Component } from 'react'
    export class StepOne extends Component {
        
        state = {
    flagImg: '',
};
render() {
    const { values, handleChange, countryFlagHandler } = this.props;

    const selectCountryChange = () => {
        const img = document.querySelector('#img');
        const select = document.querySelector('#country');
        img.src = `https://flagpedia.net/data/flags/h80/${select.selectedOptions[0].dataset.countrycode.toLowerCase()}.webp`;

        this.setState({
            flagImg: `https://flagpedia.net/data/flags/h80/${select.selectedOptions[0].dataset.countrycode.toLowerCase()}.webp`
        });
        countryFlagHandler()
    };
    return (
        <div>
            <div class="image" onChange={selectCountryChange}>  
                <img src={values.CountryFlag} id="img"/>  
            </div>
            <select id="country" onChange={handleChange('select')} defaultValue={values.select}>  
                <option data-countryCode="IN" value="91">India</option>  
                <option data-countryCode="US" value="1">US</option>  
                <option data-countryCode="GB" value="44">UK</option>  
            </select> 
        </div>
    )
}
    }
    
    export default StepOne

what I'm trying to do is actually to sync and persist the data of <Select/> and <img> in Multiform.js Component as typically what we see in a stepper form.

But, As in the StepOne

<img src={values.CountryFlag} id="img"/>

the img.src is actually manipulated by the function selectCountryChange and to keep the value of img.src persisted I thought of creating countryFlagHandler in Multiform and importing it to StepOne

but when i selected any value, it gave me this error:

TypeError: Cannot read property 'flagImg' of undefined

Registration.countryFlagHandler
C:/Users/Rahul/Desktop/cfm-usersignup/src/public/form/registration.js:53
  50 |   this.setState({ [input]: e.target.value });
  51 | };
  52 | countryFlagHandler = () =>{
> 53 |   this.setState({CountryFlag : this.props.state.flagImg})
     | ^  54 | }
  55 | 
  56 |

&

selectCountryChange
C:/Users/Rahul/Desktop/cfm-usersignup/src/public/form/credential.js:31
  28 |  this.setState({
  29 |      flagImg: `https://flagpedia.net/data/flags/h80/${select.selectedOptions[0].dataset.countrycode.toLowerCase()}.webp`
  30 |  });
> 31 |  countryFlagHandler();
     | ^  32 | };
  33 | return (
  34 |  <div>

Can anyone please tell me how to rectify my error?

You can also checkout my project repo for more info.

2 Answers 2

1
+50

Shor answer

You're getting an error because countryFlagHandler is not getting the value it's expected, it doesn't have access to the StepOne component's state. You would need to pass the value as an argument to the parent component.

  // flagImg will come as an argument from the child Component
   countryFlagHandler = (flagImg) =>{
      this.setState({ CountryFlag : flagImg })
   }

    const selectCountryChange = () => {
    const img = document.querySelector('#img');
    const select = document.querySelector('#country');
    img.src = `https://flagpedia.net/data/flags/h80/${select.selectedOptions[0].dataset.countrycode.toLowerCase()}.webp`;

    this.setState({
        flagImg: `https://flagpedia.net/data/flags/h80/${select.selectedOptions[0].dataset.countrycode.toLowerCase()}.webp`
    });
    const countryFlag = `https://flagpedia.net/data/flags/h80/${select.selectedOptions[0].dataset.countrycode.toLowerCase()}.webp`;
    // CountryFlag would be passed as an argument
    countryFlagHandler(countryFlag);
   };

Long Answer

I would recommend refactoring your code a bit and moving all the data to the parent component rather than keeping them in two different states. And also one function would be enough to handle all the data manipulation.

Parent Component Multiform

import React, { Component } from 'react'
import StepOne from './StepOne'
export class Multiform extends Component {
state = {
    step: 1,
    CountryFlag: 'https://raw.githubusercontent.com/MeRahulAhire/country-calling-code-html/master/phone_icon.png',
    CountryCode: ''
};

handleSelectChange = (event) => {
    const value = event.target.value;
    const countryCode = event.target[event.target.selectedIndex].getAttribute('data-country-code');
    const countryFlagImg = `https://flagpedia.net/data/flags/h80/${countryCode.toLowerCase()}.webp`;

    this.setState({
        select: value,
        CountryFlag: countryFlagImg
    });
}

render() {
    const { CountryFlag, CountryCode, step } = this.state;
    const values = { CountryFlag, CountryCode };

    switch (step) {
        case 1:
            return (
                <StepOne
                    handleChange={this.handleSelectChange}
                    countryFlagHandler={this.countryFlagHandler}
                    values={values}
                />

            )
        default:
            return (<h1>hello React App</h1>)

    };
  }
}

And child Component StepOne

import React, { Component } from 'react'
class StepOne extends Component {
render() {
    const { values, handleChange } = this.props;

    return (
        <div>
            <div class="image">
                <img src={values.CountryFlag} id="img" />
            </div>

            <select id="country" onChange={this.props.handleChange} defaultValue={values.select}>
                <option data-country-code="IN" value="91">India</option>
                <option data-country-code="US" value="1">US</option>
                <option data-country-code="GB" value="44">UK</option>
            </select>
        </div>
    )
  }
}

Running Example

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

Comments

1

As I see from your code, you try to get the flagImg propery from object that doesn't exist. If I understood your logic correctly, you need to update the selectCountryChange and countryFlagHandler methods:

const selectCountryChange = () => {
    const img = document.querySelector('#img');
    const select = document.querySelector('#country');
    const flagImg = `https://flagpedia.net/data/flags/h80/${select.selectedOptions[0].dataset.countrycode.toLowerCase()}.webp`;
    img.src = flagImg;

    this.setState({
        flagImg
    });
    countryFlagHandler(flagImg)
};

and in the countryFlagHandler method take it from argumnets:

countryFlagHandler = CountryFlag =>
    this.setState({ CountryFlag })

Also, your logic looks pretty dirty, maybe you could generate the flagImg property, when you select a country, set it to Multiform, and finally pass it through props to StepOne.

Comments

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.