简体   繁体   English

处理在React中单击外部组件

[英]Handle Click outside Component in React

Currently I have a search bar which toggles "Results" component on formSubmit. 目前我有一个搜索栏,可以在formSubmit上切换“结果”组件。 I am looking for a React approach to handle clicks() outside "results" to hide it. 我正在寻找一种React方法来处理“结果”之外的点击()来隐藏它。 The problem is that when I go to another page, or when I click anywhere my "results" keep showing. 问题是当我去另一个页面,或者当我点击任何地方时,我的“结果”会继续显示。

I've tried using CSS stuff like focusOutside, but that's not my way. 我尝试过使用像focusOutside这样的CSS之类的东西,但这不是我的方式。

Search.js 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 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 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;

and searchAction.js 和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()));
};
};

As I see my behavior gives me only two solutions one of is just hiding element and the second is sending a null query, which make no sense to me, there should be a better way? 正如我看到我的行为只给我两个解决方案,其中一个只是隐藏元素,第二个是发送一个空查询,这对我没有意义,应该有更好的方法吗?

You can register EventListener on click to body element at componentDidMount hook. 您可以在componentDidMount挂钩的click to body元素上注册EventListener。 Сheck outside clicks and don't forget remove EventListener at componentWillUnmount hook. Сheck外部点击,不要忘记在componentWillUnmount钩子上删除EventListener。

You can put an overlay around your search box, something like this: 您可以在搜索框周围放置一个叠加层,如下所示:

// 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>
}

I've solved it by two steps, first was adding [ https://github.com/airbnb/react-outside-click-handler ] to my project and adding a isSearchResultsVisible bool variable. 我已经通过两个步骤解决了这个问题,首先是在我的项目中添加[ https://github.com/airbnb/react-outside-click-handler ]并添加一个isSearchResultsVisible bool变量。

Search.js file 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);

add

export const SET_SEARCH_RESULTS_VISIBILITY = 'SET_SEARCH_RESULTS_VISIBILITY';

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

to action file and 动作文件和

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

to reducer. 减速机。 In case anyone's reading this, don't forget to import SET_SEARCH_RESULTS_VISIBILITY to reducer and to add isSearchResultsVisible: false, to initialState 如果有人在阅读本文,请不要忘记将SET_SEARCH_RESULTS_VISIBILITY导入reducer并将isSearchResultsVisible: false,添加到initialState

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM