Lacon
Lacon

Reputation: 105

React - change css style in one specific state

What I want to do is to change the border on the input to red if the input value doesn't match any movie in the API call. The user types in the input field and the call to the API shows the matching result. If we don't have any result I would like the border on the input to be red. But I can't see how I should make that happen.

The component Input is at the end of the code snippet.

CSS

.input-style {
  padding: 7px;
  border-radius: 5px;
  border: 1px solid #cccccc;
  font-family: Courier New, Courier, monospace;
  transition: background-color 0.3s ease-in-out;
  outline: none;
}
.input-style:focus {
  border: 1px solid turquoise;
}

APP.js

class App extends Component {
  constructor() {
    super();

    this.state = {
      value: '',
      items: [],
      isLoading: false,
      searchResult: null,
      error: false,
    };
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  // To handle search
  handleChange(e) {
    this.setState({ value: e.target.value });
  }
  handleSubmit(e) {
    let searchResult = [];
    for (var i = 0; i < this.state.items.length; i++) {
      if (
        this.state.items[i].name
          .toLowerCase()
          .indexOf(this.state.value.toLowerCase()) !== -1
      ) {
        searchResult.push(this.state.items[i]);
      } else {
        console.log('No matches on your search, try again');
      }
    }
    e.preventDefault();

    // If we have something in the object searchResult
    if (searchResult.length > 0) {
      this.setState({
        error: false,
        value: '',
        searchResult: searchResult,
      });
    } else {
      this.setState({
        error: true,
        value: '',
        searchResult: [],
      });
    }
  }

  // call to the API
  componentDidMount() {
    this.setState({ isLoading: !this.state.isLoading });
    fetch('https://api.tvmaze.com/shows')
      .then(response => response.json())
      .then(data => {
        this.setState({
          items: data,
          error: false,
        });
        this.setState({ isLoading: !this.state.isLoading });
      })
      .catch(console.error);
  }

  render() {
    return (
      <div className="App">
        <Header />

        <Loader isLoading={this.state.isLoading} />

        <Input
          handleChange={this.handleChange}
          handleSubmit={this.handleSubmit}
          value={this.state.value}
        />

        {this.state.error ? (
          <p className="errorMsg">No match on the search, try again!</p>
        ) : null}

        <Search search={this.state.searchResult} />
      </div>
    );
  }
}

export default App;

Input.js

function Input(props) {
  return (
    <div>
      <form onSubmit={props.handleSubmit}>
        <input
          type="text"
          className="input-style"
          placeholder="Sök efter film.."
          value={props.value}
          onChange={props.handleChange}
        />

        <button id="bold" className="button-style" type="submit">
          <i className="fa fa-search" />
        </button>
      </form>
    </div>
  );
}
export default Input;

Upvotes: 3

Views: 10581

Answers (3)

justarandomguy
justarandomguy

Reputation: 331

You can easily do this. Have a flag, say resultFound, in the state of App.js, with an initial value of false. Then, in the function where you make the API call, update this resultFound depending on whether any result was obtained.

And, in the render(), before returning, assign inputClassName dynamically based on the this.state.resultFound, like so,

let inputClassName = '';
if (this.state.resultFound === false) {
  inputClassName = 'input-style-error';       // new CSS class for errors
} else {
  inputClassName = 'input-style';
}

Then, you can pass the inputClassName as a prop to Input and use it as <input>'s className, like so,

// in your App.js render() method's return
// ... existing components
<Input customStyle={inputClassName} ... />
// ...
<!-- in your Input.js render() method -->
<input type="text" className={props.customStyle} ... />

Whenever the API call happens, your state will change causing a re-render of the DOM (render() is called). During each call, we dynamically set the inputClassName based on the state's resultFound. And, accordingly, the right className will be applied to the <input>.

Upvotes: 2

Hinrich
Hinrich

Reputation: 13983

I will give bad names for classes and variables, just to make it super clear. You should use more generic ones.

The trick here is to give your Input a dynamic class via props, and if that expression turns true and the class is appended to the element, you can style it with css.

__CSS__

    .input-style {
      padding: 7px;
      border-radius: 5px;
      border: 1px solid #cccccc;
      font-family: Courier New, Courier, monospace;
      transition: background-color 0.3s ease-in-out;
      outline: none;
    }
    .input-style:focus {
      border: 1px solid turquoise;
    }
    .input-style.red-border {
      border: 1px solid red;
    }

__APP.js__   

    class App extends Component {
      constructor() {
        super();

        this.state = {
          value: '',
          items: [],
          isLoading: false,
          searchResult: null,
          error: false,
        };
        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
      }

      // To handle search
      handleChange(e) {
        this.setState({ value: e.target.value });
      }
      handleSubmit(e) {
        let searchResult = [];
        for (var i = 0; i < this.state.items.length; i++) {
          if (
            this.state.items[i].name
              .toLowerCase()
              .indexOf(this.state.value.toLowerCase()) !== -1
          ) {
            searchResult.push(this.state.items[i]);
          } else {
            console.log('No matches on your search, try again');
          }
        }
        e.preventDefault();

        // If we have something in the object searchResult
        if (searchResult.length > 0) {
          this.setState({
            error: false,
            value: '',
            searchResult: searchResult,
          });
        } else {
          this.setState({
            error: true,
            value: '',
            searchResult: [],
          });
        }
      }

      // call to the API
      componentDidMount() {
        this.setState({ isLoading: !this.state.isLoading });
        fetch('https://api.tvmaze.com/shows')
          .then(response => response.json())
          .then(data => {
            this.setState({
              items: data,
              error: false,
            });
            this.setState({ isLoading: !this.state.isLoading });
          })
          .catch(console.error);
      }

      render() {
        return (
          <div className="App">
            <Header />

            <Loader isLoading={this.state.isLoading} />

            <Input
              handleChange={this.handleChange}
              handleSubmit={this.handleSubmit}
              value={this.state.value}
              showRedBorder={this.state.error === true} // or what ever your logic
            />

            {this.state.error ? (
              <p className="errorMsg">No match on the search, try again!</p>
            ) : null}

            <Search search={this.state.searchResult} />
          </div>
        );
      }
    }

    export default App;

__Input.js__

    function Input(props) {
      return (
        <div>
          <form onSubmit={props.handleSubmit}>
            <input
              type="text"
              className={`input-style${props.showRedBorder ? ' red-border' : ''}`}
              placeholder="Sök efter film.."
              value={props.value}
              onChange={props.handleChange}
            />

            <button id="bold" className="button-style" type="submit">
              <i className="fa fa-search" />
            </button>
          </form>
        </div>
      );
    }
    export default Input;

Upvotes: 1

Jee Mok
Jee Mok

Reputation: 6556

You can pass the error into the Input component from the App

<Input
   handleChange={this.handleChange}
   handleSubmit={this.handleSubmit}
   value={this.state.value}
   error={this.state.error)
/>

and in your Input component:

 <input
   type="text"
   className={props.error ? 'error-input-style' : 'input-style'}
   placeholder="Sök efter film.."
   value={props.value}
   onChange={props.handleChange}
 />

alternative you can also set an inline styling for the error condition:

 <input
   type="text"
   className="input-style"
   placeholder="Sök efter film.."
   value={props.value}
   onChange={props.handleChange}
   style={{ border: props.error ? '1px solid red' : '' }}
 />

Upvotes: 3

Related Questions