Search code examples
javascriptreactjsreact-propsconfirmation

React Props doesnt pass info to the confirmation page


I am new to React, and I am designing a submission form with multiple fields in React. It has some Textfield input boxes and a Tags input feature. The user enters their info in fields and at the end, the info has to be shown on a confirmation page. Now all of my info are shown except for the Tags (from the tags input). What seems to be the problem?

The Form (AdditionalInfo.js)

export const TagsInput = props => {
  const [Tags, setTags] = React.useState(props.Tags)
  const removeTags = indexToRemove => {
    setTags([...Tags.filter((_, index) => index !== indexToRemove)])
  }
  const addTags = event => {
    if (event.target.value !== '') {
      setTags([...Tags, event.target.value])
      props.selectedTags([...Tags, event.target.value])
      event.target.value = ''
    }
  }
  return (
    <div className='tags-input'>
      <ul id='tags'>
        {Tags.map((Tag, index) => (
          <li key={index} className='tag'>
            <span className='tag-title'>{Tag}</span>
            <span
              className='tag-close-icon'
              onClick={() => removeTags(index)}
            >
                             x
            </span>
          </li>
        ))}
      </ul>
      <input
        type='text'
        onKeyUp={event => event.key === 'Enter' ? addTags(event) : null}
        placeholder='Press enter to add tags'
      />
    </div>
  )
}

export class AdditionalInfo extends Component {
  continue = e => {
    e.preventDefault();
    this.props.nextStep();
  };

  back = e => {
    e.preventDefault();
    this.props.prevStep();
  };

 render() {
    const {values, handleChange} = this.props
    const selectedTags = Tags => {
      console.log(Tags)
    }
    return (
        <Container className='ContainerA'>
          <Row>
           <Col className='ACol'>
         <br />
         <br />
          <div>
          <TagsInput selectedTags={selectedTags} Tags={['Nodejs', 'MongoDB']} onChange={handleChange('Tags')}
           defaultValue={values.Tags} />
          </div>
        <Row>
        <Col xs='6' sm='4'>
        <TextField
        placeholder="Country"
        label="Country"
        onChange={handleChange('Country')}
        defaultValue={values.Country}
        margin="normal"
        fullWidth="true"
        id="outlined-basic" 
        variant="outlined"
        required
      />
....
     <div className='buttons-container' style={{position:'relative',bottom:'20px'}}>
       <button onClick={this.back} className='previous'>قبلی</button> 
        <button form='my-form' type='submit' onClick={this.continue} className='next'>ادامه</button>
    </div>

The Main Form Holder

//AdditionalInfo.js is used here

class CreateJob extends Component {
  state = {
    step:1,
    Title:'',
    requirements:'',
    Country:'',
    Region:'',
    Zipcode:'',
    Benefits:'',
    Company:'',
    InternalCode:'',
    Details:'',
    Tags:[],
    Address:'',
    Department:'',
    Salary:''
  }

  nextStep =() => {
    const {step} = this.state
    this.setState({
      step: step + 1
    })
  }
  prevStep =() => {
   const {step} = this.state
   this.setState({
     step: step - 1
   })
 }

 handleChange = input => e => {
  this.setState({ [input]: e.target.value });
};


  render () {
    const { step } = this.state
    const {Title,Benefits,Company,InternalCode,Detailss,Tags,Address,Department,Salary,requirements,Country,Region,Zipcode } = this.state;
    const values ={Title,Benefits,Company,InternalCode,Detailss,Tags,Address,Department,Salary,requirements,Country,Region,Zipcode}


    return (
      <div>
........
{(()=>{
          switch (step) {
            case 1:
              return(
                <AdditionalInfo
                  nextStep={this.nextStep}
                  handleChange={this.handleChange}
                  values={values}
                />
              )
.........

And this is the confirmation Page

//it uses the main form holder component info too

export class Confirmation extends Component {

  continue = e => {
    e.preventDefault();
    this.props.nextStep();
  };

  back = e => {
    e.preventDefault();
    this.props.prevStep();
  };
  render () {
    const {
      values: {
        Title, Benefits,
        Company, InternalCode, Detailss, Department,Tags, Salary,requirements,Zipcode,
        Country,Region
      }
    } = this.props
    return (
....

        <div className='content'>
          <Container>
            <Row>
              <Col xs='6' sm='4' className='TextContent'>
                <Row className='TextInside'> {Salary} : حقوق پیشنهادی</Row>
                <Row>  زیپ کد : {Zipcode}</Row>
                <Row>  کشور : {Country} </Row>
                ..
              </Col>
              <Col xs='6' sm='4' className='TextContent'>
              ...
                <Row>   تگ ها : {Tags}</Row>

Sorry for the long code but I don't understand why the info of {Country} (for example) is shown in this Confirmation page but my tags from the TagsInput {Tags} aren't displayed (even though they are logged correctly into the Console) Where is my mistake?


Solution

  • Your AdditionalInfo component ties each of its inputs to the handleChange props, which was passed to it by the parent. When a field is updated, the parent learns about it.

    Your TagsInput component stores the list of tags in its own state (useState hook). It never informs the parent about any changes it has.

    Later, when your parent (presumably, not shown) calls the ConfirmationPage, it doesn't know about the tags that were entered, but it does know about the other info because it was told about them.

    Instead, track the list of tags in the parent, and pass down the current list of tags (as well as an 'addTag' and 'removeTag' handler) to the child.

    UPDATE

    I have edited your sandbox to show a working example. Note I did have to remove a lot of references to outside files to get it to compile, so in the future you may want to strip down your code to the exact question you have before posting the sandbox.

    https://codesandbox.io/s/tagger-u8fs5

    Some notes:

    1. It wasn't clear before how your file structure was working. Now that I see it, you don't need to actually move the state of tags. It's fine to have the TagsInput track its own state. But it is still true that you need to update its parent with this value. To do this, I added a updateParent function that is called whenever you add or remove a tag.
    2. The handleChange function couldn't be used for the tags, because you have it set up to look for e.target.value and in this case we are sending it an array of values directly, not an event with a value. I added a handleChangeRaw function that allows this.
    3. When you update the state of the tags (add/remove) I store the new temporary array in a constant that can then also be sent to the parent. You don't want to just set it directly (i.e. with setTags) and then have the updateParent function reference that state directly, because it will be one render behind (i.e. one tag behind)
    4. On your summary page, I added a .join(',') to your tags output; otherwise it just tries to make a string out of the elements, which will run the words together.
    5. I'd avoid setting the value of e.target.value to "" in order to reset the input field. That's a little backwards to me (you're changing the event that was a moment in time). Instead, I have your code using the input as a "controlled" input, meaning we track its state and change it whenever we want (as when we need to reset it). This is separate from tracking the list of tags; here we are just tracking the current value of the input box.

    So in summary, the missing step was indeed that your parent component was never made aware of the changes in the tags. When you have nested components (parent/child relationship) you need to carefully pass the information up and down; they don't automatically share what they know.