How destructive would it be to pass a React component function that allows its children to set any of its state?

I’m just musing here. Say something like:

function changeState(stateKey, stateValue){
  let newPair = {};
  newPair[stateKey] = stateValue;

  this.setState(newPair};
}

render(){
   <Child changeParentState={this.changeState} />
}

Assuming both stateKey and stateValue are strings and Child is heavily monitored.

Does this give way too much power to children components? Is it just way too vague to be allowed? I’m not actually going to do it, but passing down multiple similar functions also looks kinda of ugly.

Not destructive. Child components altering parent state is a common pattern

2 Likes

Okay but I mean an all-purpose function that appears exactly as I’ve written it instead of one that’s actually named appropriately. It’s common for child components to alter its parent state but usually it’s using a specific function that only alters a specific state. Wouldn’t it break some sort of convention to have something this general?

After reconsidering I’ve created a similar function but limited its power by returning false and logging a message if it tries to change irrelevant states.

Problem solved I guess, but I’m still open to opinions on this matter.

I don’t think there was a problem to begin with. As @Swoodend has already helpfully pointed out, what you posted initially is a common pattern; in fact, it’s used in Thinking in React in the React documentation—have a look at how the search bar functions.

If you are really not comfortable with this pattern, which I think is understandable (particularly if you have many components that use this pattern), you could consider using Redux to manage all states that are shared between components. Having said that, even when I’m using Redux I still use the patten in question to manage states with limited scope without Redux, which is perfectly fine to do so as Dan Abramov has pointed out.

Quoting what Dan Abramov said in that reference:

The rule of thumb is: do whatever is less awkward.

2 Likes

Sorry, I feel like I still haven’t made my question clear.

Using your link as an example, the difference is that the functions are named and only take in one parameter, which becomes the value in the key-value pair. The key remains constant because an object literal property name isn’t computed by default. The key is decided entirely by the parent.

From the example:

  handleFilterTextChange(filterText) {
    this.setState({
      filterText: filterText
    });
  }
  
  handleInStockChange(inStockOnly) {
    this.setState({
      inStockOnly: inStockOnly
    })
  }

This could be rearranged and combined to a single function that takes in two parameters and computes the key/property name.

function changeState(stateKey, stateValue){ 
  let newPair = {};
  newPair[stateKey] = stateValue;

  this.setState(newPair);
}

Or more concise

function changeState(stateKey, stateValue){ 
  this.setState({[stateKey]: stateValue});
}

So instead of a child component appropriately calling either handleFilterTextChange or handleInStockChange, it would instead call changeState for both. Instead of the parent having complete control over which of its state can be changed, the children can now change any of its parent state with a universal, generic state changer function.

  handleFilterTextChange(e) {
    this.props.onFilterTextChange(e.target.value);
  }
  
  handleInStockChange(e) {
    this.props.onInStockChange(e.target.checked);
  }

becomes

  handleFilterTextChange(e) {
    this.props.changeState("filterText", e.target.value);
  }
  
  handleInStockChange(e) {
    this.props.changeState("inStockOnly", e.target.checked);
  }

In this scenario that becomes 1 less function to make and 1 less function to bind at the expense of specifying string arguments inside the child function, but theoretically it could remove the hassle of declaring/binding an arbitrary number of functions (assuming that all they’re doing is changing a single state). Yet something about this just feels wrong.

Ah, I see what you mean now and many apologies for not having addressed that earlier. I don’t think there is anything wrong in the last examples you have given (I’m still learning myself so feel free to take this with a grain of salt), but perhaps passing the event to the parent and processing it there may make you feel more comfortable (I personally think it’s clearer):

Parent Component

const changeState = (event) => {
  const {name, value, checked} = event.target;

  // You can do other things here if necessary

  this.setState({
    [name]: name === 'inStockOnly' ? checked : value
  });

  // Assuming that the checkbox input inside the child
  // component is something like
  // <input type="checkbox" name="inStockOnly" onChange={this.handleChange} />
  // This is just one way to handle different inputs using the name attribute,
  // there are probably better ways to do it.
};

// ...

render() {
  return(
    <Child onAnyInputChange={this.changeState} />
  );
}

The first time I had to do something like this was when I was building a contact form and I wanted to group a few text inputs inside a single component to be reused later (name, e-mail address and message). It felt wrong for inexplicable reasons at the beginning and I’m not entirely sure why I felt that way. Other than me being a paranoid, I couldn’t come up with a good reason of why it felt wrong, it just… somehow felt wrong.

Anyhow, I hope this response is a bit more helpful! And on a somewhat related note, you may find this article hepful. Good luck! :slight_smile:

Hi @honmanyau not to hijack the thread but does this type of assignment have a name?

const {name, value, checked} = event.target;

I know what it does but never know what to call it

Thanks!

I also learnt how to use it before I knew what it’s called. xD It’s called destructuring assignment—here’s the MDN reference!

1 Like

Thank you very much :joy:! Been looking for this forever!

1 Like