处理React中组件外部的点击

时间:2019-04-26 15:11:10

标签: javascript reactjs

当前,我有一个搜索栏,用于切换formSubmit上的“结果”组件。 我正在寻找一种React方法来处理“结果”外部的clicks()以隐藏它。 问题是当我转到另一个页面或单击任何地方时,我的“结果”一直显示。

我已经尝试过使用诸如focusOutside之类的CSS东西,但这不是我的方式。

Search.js

import React, {Component} from 'react';
import {Container, Icon} from 'semantic-ui-react';
import {connect} from 'react-redux';
import {searchAll} from './modules/searchAction';
import SearchResult from './SearchResult';
import MyLoader from "../../components/MyLoader";

import "../../styles/layout/_search.scss"


class Search extends Component {
state = {
    query: null,
};

handleInputChange = (e) => {
    this.setState({query: e.target.value})
};

handleSubmit = (e) => {
    e.preventDefault();
    this.state.query === null || undefined || '' ?
        (alert('wrong input')) :
        (this.props.searchAll(this.state.query));
};

render() {
    const {error, loading, result} = this.props;
    const filteredResult = result.filter(item => item.poster_path && (item.name || item.title));
    if (error) {
        console.log(error);
    }
    if (loading) {
        return <MyLoader/>;
    }

    return (
        <div className="search_area">
            <Container className="primary-container">
                <form className='search_form' onSubmit={this.handleSubmit}>
                    <Icon name='search'
                          size="large"
                          className='search_icon'/>
                    <input type='text'
                           className='search_input'
                           placeholder="Search for a Movie, Tv Show or Person"
                           onChange={this.handleInputChange}/>
                </form>
                <div className="results_area">
                    {filteredResult.map(suggestion => {
                        return (
                            <SearchResult
                                key={suggestion.id}
                                title={suggestion.title}
                                name={suggestion.name}
                                release_date={suggestion.release_date}
                                media_type={suggestion.media_type}
                                path={suggestion.poster_path}
                            />
                        )
                    })}
                </div>
            </Container>
        </div>
    )
};
}

const mapStateToProps = state => ({
   result: state.suggestions.suggestions,
   loading: state.suggestions.loading,
   error: state.suggestions.error
});

const mapDispatchToProps = {
   searchAll,
};

export default connect(mapStateToProps, mapDispatchToProps)(Search);

SearchResult.js

import React from "react";
import {DEFAULT_IMG_URL} from "../../const";
import {SMALL_IMG} from "../../const";
import {Image} from "semantic-ui-react";
import "../../styles/layout/_search.scss"

const SearchResult = (props) => {

let title = null;
let release = null;
let type = null;

let imageLink = DEFAULT_IMG_URL + SMALL_IMG + props.path;

switch (props.media_type) {
    case "movie": {
        type = "Movie";
        break;
    }
    case "tv": {
        type = "TV";
        break;
    }
    case "person": {
        type = "Person";
        break;
    }
    default: {
        type = "TBD";
        break;
    }
}

props.title === undefined ?
    (title = "N/A"):
    (title = props.title);

props.release_date === undefined ?
    (release = "N/A"):
    (release = props.release_date);


return (
    <div className="suggestion-body">
        <Image className="suggestion-image"
               src={imageLink}>
        </Image>
        <div className="suggestion-info">
            <div className="suggestion-title">
                <h2>{title}</h2>
            </div>
            <div className="suggestion-year">
                <h4>{release}</h4>
            </div>
        </div>
        <div className="suggestion-type">
            {type}
        </div>
    </div>
);
};

export default SearchResult;

searchReducer.js

import {
  SEARCH_ALL_BEGIN,
  SEARCH_ALL_SUCCESS,
  SEARCH_ALL_FAILURE
} from "./searchAction";

const initialState = {
  suggestions: [],
  loading: false,
  error: null
  //suggestions true/false
};

const searchReducer = (state = initialState, action) => {
switch(action.type) {
    case SEARCH_ALL_BEGIN:
        return {
            ...state,
            loading: true,
            error: null
        };

    case SEARCH_ALL_SUCCESS:
        return {
            ...state,
            loading: false,
            suggestions: action.suggestions
        };

    case SEARCH_ALL_FAILURE:
        return {
            ...state,
            loading: false,
            error: action.error,
        };

    default:
        return state;
}
};

export default searchReducer;

和searchAction.js

import axios from 'axios/index';
import {KEY} from "../../../key";
import {DEFAULT_URL} from "../../../const";


export const SEARCH_ALL_BEGIN = 'SEARCH_ALL_BEGIN';
export const SEARCH_ALL_SUCCESS = 'SEARCH_ALL_SUCCESS';
export const SEARCH_ALL_FAILURE = 'SEARCH_ALL_FAILURE';

export const searchAllBegin = () => ({
  type: SEARCH_ALL_BEGIN
});

export const searchAllSuccess = suggestions => ({
  type: SEARCH_ALL_SUCCESS,
  suggestions
});

export const searchAllFailure = error => ({
  type: SEARCH_ALL_FAILURE,
  error
});

export const searchAll = (query) => {
return dispatch => {
    let url = DEFAULT_URL + `search/multi?api_key=` + KEY + `&language=en-US&query=` + query + `&page=1&include_adult=false`;
    dispatch(searchAllBegin());
        axios.get(url)
        .then(result => {
            dispatch(searchAllSuccess(result.data.results));
        })
        .catch(error => dispatch(searchAllFailure()));
};
};

正如我所见,我的行为仅给我两个解决方案,一个是隐藏元素,第二个是发送空查询,这对我来说没有意义,应该有更好的方法吗?

3 个答案:

答案 0 :(得分:0)

您可以在componentDidMount钩子上单击到body元素时注册EventListener。 Сheck外部单击,不要忘记删除componentWillUnmount钩子上的EventListener。

答案 1 :(得分:0)

您可以在搜索框周围放置一个叠加层,如下所示:

// style
.overlay {
  background-color: transparent;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  position: absolute;
  z-index: 1200;
}
close(e) {
    // this is necesary to no close if the search box is clicked
    if (e && e.target !== e.currentTarget) {
        return;
    }
    // my close stuff
}
render() {
    return <div className="overlay" style={{height: document.body.scrollHeight}} 
        onClick={e => this.close(e)}>
        <div className="searchbox">
            My searchbox stuff...
        </div>
    </div>
}

答案 2 :(得分:0)

我已经通过两个步骤解决了这个问题,首先是将[https://github.com/airbnb/react-outside-click-handler]添加到我的项目中,然后添加一个isSearchResultsVisible bool变量。

Search.js文件

 import React, {Component} from 'react';
 import {Container, Icon} from 'semantic-ui-react';
 import {connect} from 'react-redux';
 import {searchAll, setSearchResultsVisibility} from './modules/searchAction';
 import SearchResult from './SearchResult';
 import MyLoader from "../../components/MyLoader";
 import "../../styles/layout/_search.scss"

 class Search extends Component {
 state = {
    query: null,
};

handleInputChange = (e) => {
    this.setState({query: e.target.value})
};

handleSubmit = (e) => {
    e.preventDefault();
    if(this.state.query) {
        this.props.searchAll(this.state.query);
        this.props.setSearchResultsVisibility(true);
    }
};

render() {
    const {error, loading, result, isSearchResultsVisible} = this.props;
    const filteredResult = result.filter(item => item.poster_path && (item.name || item.title));

    if (error) {console.log(error)}
    if (loading) {return <MyLoader/>}

    return (
        <div className="search_area">
            <Container className="primary-container">
                <form className="search_form" onSubmit={this.handleSubmit}>
                    <Icon name="search"
                          size="large"
                          className="search_icon"
                    />
                    <input type="text"
                           className="search_input"
                           placeholder="Search for a Movie, Tv Show or Person"
                           onChange={this.handleInputChange}
                    />
                </form>
                {
                    isSearchResultsVisible &&
                    <SearchResult result={filteredResult} />
                }
            </Container>
        </div>
    )
};
}

const mapStateToProps = state => ({
result: state.suggestions.suggestions,
loading: state.suggestions.loading,
error: state.suggestions.error,
isSearchResultsVisible: state.suggestions.isSearchResultsVisible,
});

const mapDispatchToProps = {
searchAll,
setSearchResultsVisibility,
};

export default connect(mapStateToProps, mapDispatchToProps)(Search);

添加

export const SET_SEARCH_RESULTS_VISIBILITY = 'SET_SEARCH_RESULTS_VISIBILITY';

export const setSearchResultsVisibility = isSearchResultsVisible => ({
type: SET_SEARCH_RESULTS_VISIBILITY,
isSearchResultsVisible
});

操作文件和

case SET_SEARCH_RESULTS_VISIBILITY:
        return {
            ...state,
            isSearchResultsVisible: action.isSearchResultsVisible,
        };

减速器。 如果有人正在阅读此书,请不要忘记将SET_SEARCH_RESULTS_VISIBILITY导入reducer并将isSearchResultsVisible: false,添加到initialState