0

I am a beginner of React JS. Messing around to achieve VanJS event listeners.

For layout, I decide to store things like a panel, a button as individual component.

Now, I have a component Movie and another component Button, how can I trigger state change of Movie by Button.onclick()?.

In other words, how to modify a component by the event happening on another component?

Before posting, I have read:

but tons of method followed by really confused me.

  • useState
  • componentWillMount: immediately before initial rendering
  • componentDidMount: immediately after initial rendering
  • componentWillReceiveProps: when component receives new props
  • shouldComponentUpdate: before rendering, after receiving new props or state
  • componentWillUpdate: before rendering, after receiving new props or state.
  • componentDidUpdate: after component's updates are flushed to DOM
  • componentWillUnmount: immediately before removing component from DOM

Following is a demo, which contains a Movie card and a Button, when the button is clicked, I want the background colour of the Movie card to change

React TypeScript Code Sandbox

the code:

import * as React from "react";
import "./styles.css";
import styled from 'styled-components';

function CSSPropertiesToComponent(dict:React.CSSProperties){
  let str = '';
  for(const [key, value] of Object.entries(dict)){
      let clo = '';
      key.split('').forEach(lt=>{
          if(lt.toUpperCase() === lt){
              clo += '-' + lt.toLowerCase();
          }else{
              clo += lt;
          }
      });
      str += clo + ':' + value + ';';
  }
  return str;
}

class Movie extends React.Component<any, any>{
  public static style:React.CSSProperties|object = {
      width: "300px",
      height: "120px",
      display: "flex",
      justifyContent: "space-around",
      alignItems: "center",
      borderRadius: "20px",
      filter: "drop-shadow(0px 1px 3px #6d6d6d)",
      WebkitFilter: "drop-shadow(3px 3px 3px #6d6d6d)",
      backgroundColor: '#'+Math.floor(Math.random()*16777215).toString(16),
      fontSize: '2.5rem',
      margin: '20px',
      color: '#fff',
  }

  props:React.ComponentProps<any>;
  state:React.ComponentState;

  constructor(props:any) {
      super(props);
      this.props = props;
      this.state = {style: Object.assign({}, Movie.style)}
      this.changeColor = this.changeColor.bind(this);
  }

  changeColor():void{
      this.setState({style: Object.assign({}, Movie.style, {backgroundColor: '#'+Math.floor(Math.random()*16777215).toString(16)})});
  }

  render():JSX.Element{
      let StyledMovie = styled.div`
          ${CSSPropertiesToComponent(Movie.style)}
      `;

      return (
          <>
              <StyledMovie>{this.props.title}</StyledMovie>
          </>
      )
  }
}

export default function App() {
  let MV = new Movie({title: 'the Avengers'});
    return (
        <>
            {MV.render()}
            <button onClick={MV.changeColor}>Change Color</button>
        </>
    )
}

However, when clicking on the change colour button, it doesn't work and shows a warning:

Warning: Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to `this.state` directly or define a `state = {};` class property with the desired state in the Movie component.

if anyone offers some suggestion, I will be so glad.

duck
  • 141
  • 1
  • 9
  • 2
    This doesn't answer your question in the nicest of ways, but it shows a very basic example using function components sharing data from a parent to a child. [https://stackblitz.com/edit/react-tswfof](https://stackblitz.com/edit/react-tswfof) – Jacob Smit Sep 10 '20 at 03:16

1 Answers1

1
let MV = new Movie({title: 'the Avengers'});
    return (
        <>
            {MV.render()}
            <button onClick={MV.changeColor}>Change Color</button>
        </>
    )

Should look like (using jsx or tsx):


onChangeColor(color) {
  setState({color: color}); // state won't be in Movie component !
}

// use React.Fragment if you don't want an additional div
return (
  <div>
    <Movie title={'the Avengers'} />
    <button onClick={onChangeColor}>Change Color</button>
  <div/>
)

Also on class Movie extends React.Component<any, any>, you are settings both generic parameters to any, the first one is the type of the "props" of the component (inputs and callbacks), and the second one is the type of the "state" of the component.


interface MovieProps {
  // ...
}

interface MovieState {
  // ...
}

class Movie extends React.Component<MovieProps, MovieState> { }

As said, looking at your code, you need to save the state outside Movie component, you might want to keep App component clean,and make another component to wrap Movie and button, that will be responsible to handle interaction between both.

There is a lot more to be said on this code, but I cannot cover all of it, I recommend you follow a tutorial to get stronger basics of React.

Ambroise Rabier
  • 3,636
  • 3
  • 27
  • 38