0

I am fairly new to React. Currently I have two React components - Article.js and ControlForm.js

My render return in Article.js is this:

  return (
    <div className="article">
      {article_wrapper.map( article =>
        <div key={article.node.nid} className="article-main-display">
       <h1 className="title" dangerouslySetInnerHTML={createMarkup(article.node.title)}/>
          <div className="img-div"><img src={article.node.field_image.src} /></div>

          <ControlForm />    

          <div dangerouslySetInnerHTML={createMarkup(article.node.field_user_hsk_level)} />;
          <div className="field-name-field-chinese">
          <div dangerouslySetInnerHTML={createMarkup(article.node.chinese)} />;
                  </div>
        </div>
      )}
    </div>
  );

The ControlForm.js has several form elements (all of which I'd like to be able to pass along if need be), but this is the main one:

        <div className="form-item form-type-select form-group">
          <label className="control-label">Font Size</label>
          <select
            value={this.state.value}
            onChange={this.handleSizeSelect}
            id="font-size"
            className="form-control form-select"
          >
            <option value="0">Small</option>
            <option value="1">Normal</option>
            <option value="2">Large</option>
            <option value="3">XL</option>
          </select>
        </div>

I'd like to be able to set a class on one of the divs in the Article.js based on changing a value in the ControlForm.js

What is the most "React" way to do this? Would creating a common parent to both be the best method (right now, their only parent in common is the main App.js)

Sorry if I don't totally understand how this is supposed to work - this is my first React app.

The class associated with the components are ControlForm and withFetching respectively.

EDIT - the demo example below works, but I have some additional issues with how to integrate it properly into my existing code. Here's the existing functions of ControlForm:

class ControlForm extends Component {
  constructor() {
    super();
    this.state = { toggleActive: false, sizeSelect: "0", speed: 1.3, volume: .6};
    this.onToggle = this.onToggle.bind(this);
    this.handleSpeedChange = this.handleSpeedChange.bind(this);
    this.handleVolumeChange = this.handleVolumeChange.bind(this);
    this.handleSizeSelect = this.handleSizeSelect.bind(this);
  }


  onToggle() {
    this.setState({ toggleActive: !this.state.toggleActive });
  }

    handleSizeSelect(event) {
    this.setState({ sizeSelect: event.target.value });
    this.setState({font: 'large-font'});
    parentMethod(event.target.value);
  }

  handlePlayClick(e) {
    e.preventDefault();
    voice.playButtonClick();

  }
  handlePauseClick(e) {
    e.preventDefault();
    voice.pauseButtonClick();

  }
  handleStopClick(e) {
    e.preventDefault();
    voice.stopButtonClick();

  }
  handleVolumeChange(event) {
      console.log(event.target.value);
    this.setState({ volume: event.target.value });
  }
  handleSpeedChange(event) {
      console.log(event.target.value);
    this.setState({ speed: event.target.value });
  }

Articles looks like this:

const withFetching = (url) => (Comp) =>
  class WithFetching extends Component {
    constructor(props) {
      super(props);

      this.state = {
        data: [],
        isLoading: false,
        error: null,
        dynamicClassName: "parentClass"
      };
      this.changeClassName = this.changeClassName.bind(this);
    }

      changeClassName(childData) {
    this.setState({
      dynamicClassName: childData
    });
  }

    componentDidMount() {
      this.setState({ isLoading: true });

      fetch(url)
        .then(response => {
          if (response.ok) {
            return response.json();
          } else {
            throw new Error('Something went wrong ...');
          }
        })
        .then(data => this.setState({ data, isLoading: false }))
        .catch(error => this.setState({ error, isLoading: false }));
    }

    render() {
        //return "test";
      return <Comp { ...this.props } { ...this.state } />
    }
  }

function createMarkup(html) {
  return {__html: html};
}

      function changeClassName(childData) {
          console.log("GETS HERE!")
    this.setState({
      dynamicClassName: childData
    });
  }

    const Articles = ({ data, isLoading, error }) => {
        console.log(data);
        console.log(isLoading);
      const article_wrapper = data.nodes || [];

      if (error) {
        return <p>{error.message}</p>;
      }

      if (isLoading) {
        return <p>Loading ...</p>;
      }

      return (
        <div className="article">
          {article_wrapper.map( article =>
            <div key={article.node.nid} className="article-main-display">
           <h1 className="title" dangerouslySetInnerHTML={createMarkup(article.node.title)}/>
              <div className="img-div"><img src={article.node.field_image.src} /></div>

              <ControlForm parentMethod={changeClassName} />

              <div dangerouslySetInnerHTML={createMarkup(article.node.field_user_hsk_level)} />;
              <div className="field-name-field-chinese">
              <div dangerouslySetInnerHTML={createMarkup(article.node.chinese)} />;
                      </div>
            </div>
          )}
        </div>
      );
    }

    export default withFetching(API)(Articles);

Sorry about all of these questions, I know a lot of this is due to unfamiliarity with React - this is the first thing I've tried to build in React

Steven Matthews
  • 9,705
  • 45
  • 126
  • 232
  • I have an answer to a similar question, though this question is about component function-sharing and not state-sharing, the principle is the same: "Correct way to share functions between components in React" https://stackoverflow.com/a/51661103/2430549 – HoldOffHunger Sep 28 '18 at 20:03

3 Answers3

1

You want to change parents from his childs.

First, you have to create a handler function at Article.js and pass it to ControlForm.js as a property. <ControlForm changeDiv={this.changeDiv} />

Then you focus on ControlForm.js, whenever you want to happen, you just execute the function you passed as a the prop changeDiv, like this.props.changeDiv()

See also possible duplicate: How to update parent's state in React?

zebnat
  • 521
  • 3
  • 13
0

Article.js ,

class Article extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      dynamicClassName: "parentClass"
    }
    this.changeClassName = this.changeClassName.bind(this);
  }

  changeClassName(childData) {
    this.setState({
      dynamicClassName: childData
    });
  }

  // user dynamicClassName wherever u want .

  return ( <
    div className = "article" > {
      article_wrapper.map(article =>
        <
        div key = {
          article.node.nid
        }
        className = "article-main-display" >
        <
        h1 className = "title"
        dangerouslySetInnerHTML = {
          createMarkup(article.node.title)
        }
        /> <
        div className = "img-div" > < img src = {
          article.node.field_image.src
        }
        /></div >

        <
        ControlForm  parentMethod={this.changeClassName} / >

        <
        div dangerouslySetInnerHTML = {
          createMarkup(article.node.field_user_hsk_level)
        }
        />; <
        div className = "field-name-field-chinese" >
        <
        div dangerouslySetInnerHTML = {
          createMarkup(article.node.chinese)
        }
        />; < /
        div > <
        /div>
      )
    } <
    /div>
  );
}

In ControlForm js ,

class ControlForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      
    }
    this.handleSizeSelect= this.handleSizeSelect.bind(this);
  }

  handleSizeSelect() {
   this.props.parentMethod(this.state.value);
  }

  render() {
    return (
     <div className="form-item form-type-select form-group">
      <label className="control-label">Font Size</label>
      <select
        value={this.state.value}
        onChange={this.handleSizeSelect}
        id="font-size"
        className="form-control form-select"
      >
        <option value="0">Small</option>
        <option value="1">Normal</option>
        <option value="2">Large</option>
        <option value="3">XL</option>
      </select>
    </div>
    );
  }
}
Beckham_Vinoth
  • 701
  • 2
  • 13
  • 39
0

you can conditionally render a class based on state and your handler was missing the values from the event on the onChange

here's a demo of dynamically changing style base on the state demo

mhkit
  • 171
  • 4
  • Ok this is awesome and works in your demo - there are two things I'm not sure how to adapt this in to the rest of my code - I'll add them shortly – Steven Matthews Sep 30 '18 at 13:57
  • have updated the code in the demo link above. some things to point out: 1. setState is asynchronous incase you were trying to do an api call after isLoading: true in your componentDidMount. 2. you called setState in your global changeClassName function which wouldn't work bc setState is a method of a React class, hence, why classes extend React.Component. I put that function in your withFetching HOC 3. when passing a prop down to a child(i.e parentMethod to ControlForm) it has to be passed in through its parent (i.e Articles) hope this makes sense – mhkit Oct 01 '18 at 22:51
  • Your demo is definitely working there, but for some reason when I make the changes to my classes, it isn't working for me. I'll play around with it and see what I'm still doing wrong as it should be displaying at this point - so I'm sure I've got some other error. Thank you very much for the demo, it shows me more or less how this should all work. – Steven Matthews Oct 02 '18 at 16:08
  • Yep, got it working. Thank you! I am going to look at your notes about how you made it work and study your code so I can figure out what you did. – Steven Matthews Oct 02 '18 at 16:51
  • one question I have - what would I do if I wanted to have more parentMethods to set variables on Article? Should I just pass my conditions back as structured data and parse it in the parent? – Steven Matthews Oct 02 '18 at 19:10
  • glad you got it working! cleaned up the demo code, again to get rid of the noise/unused code. hopefully, that will make it easier to reason about. The current parentMethod in the code is passed to the ControlForm component as a 'prop' from the Article component. You can pass as many props as you need to any child component - it's just data :) ControlForm would access that data as this.props.anotherProp if it's a Class component or props.prop2 etc if it's a stateless functional component – mhkit Oct 02 '18 at 22:40
  • do you have a specific example in mind? I could try to implement it – mhkit Oct 02 '18 at 22:42
  • I think I've figured it out, just by passing state from controlform back to article and accessing the data from there. I set the state, pass it back, and then I set the values from that state. I'm getting all of the data I need in exactly the way I need it at this point, so I think we're all good here! – Steven Matthews Oct 02 '18 at 22:53