Search code examples
reactjsdefault-valuereact-propssetstatereact-state-management

How to add image src to the state in multi step form in react


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.


Solution

  • 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