How to filter through content of component with a string passed through the props? - include

With a text field in the parent component, I want to filter through multiple child components where the string is being passed through the props. The child components have been outputted through a map function which is importing data from an API into the props also. I have the user input console logging after being passed as props to the child (searchTerm). My problem is that I can't hide the display of individual components based on the user's input. The trouble I'm having is that when one decides to hide itself - they all do. I've tried indexOf and have found include() to be more useful. I'm worried about my approach to the problem and would appreciate some wisdom from more seasoned react developers.
//parent component
render() {
return (
<React.Fragment>
<input type="text" className = {styles.searchField} id="searchField" placeholder = "Search..." onChange = {this.getUserInput}/>
<div className={styles.container}>
{this.state.importedBooks.map((element, index) => (
<Tile key ={index} searchTerm ={this.state.searchTerm} buy ={element.amazon_product_url}
img ={element.book_image} summary ={element.description} url={element.book_image}
book_author ={element.author} book_title ={element.title} />
))}
</div>
</React.Fragment>);
}
}
//child component
class Tile extends React.Component {
public state:{display:boolean} = {display:true};
public getContainerContent = () => {
const containers: NodeListOf<Element> | null = document.querySelectorAll("#container");
containers.forEach(element => {
if (element.innerHTML.toLocaleLowerCase().includes(this.props.searchTerm) !== false) {
this.setState({display:true});
}
else if (this.props.searchTerm == "") {
this.setState({display:true});
}
else {
this.setState({ display: false });
}
})
};
public componentWillReceiveProps = () => {
this.getContainerContent();
}
render() {
const renderContainer = this.state.display ? styles.container : styles.hideDisplay;
return (
<div className={styles.container} id="container">
<h1>{this.props.book_title}</h1>
<h2>{this.props.book_author}</h2>
<img src = {this.props.img} alt="book cover"/>
<p>{this.props.summary}</p>
Purchase
</div> );
}
}

I think the problem stems from the getContainerContent method in your child component.
Why would a single child component be concerned with data that belongs to other child components?
A way you can solve this is to first declare in the parent component which props are you going to match the filter against when displaying child components.
Then, you could iterate over these declared props and decide whether the component should display a child component or not.
// parent comp
private shouldDisplayElement (element, searchTerm: string): boolean {
const propsToMatchFiltersAgainst = ['book_title', 'book_author', 'buy', /* etc... */];
let shouldDisplay = false;
for (const p of propsToMatchFiltersAgainst) {
if (`${element[p]}`.toLowerCase().includes(searchTerm.trim())) {
shouldDisplay = true;
}
}
return shouldDisplay;
}
// parent's render fn
<div className={styles.container}>
{this.state.importedBooks.map((element, index) => (
this.shouldDisplayElement(element, this.state.searchTerm)
? <Tile
key={index}
buy={element.amazon_product_url}
img={element.book_image}
summary={element.description}
url={element.book_image}
book_author={element.author} book_title={element.title}
/>
: null
))}
</div>
This way, given the current situation, you don't need to use the getContainerContent method in your child component anymore.

Related

Is it possible to prevent `useLazyQuery` queries from being re-fetched on component state change / re-render?

Currently I have a useLazyQuery hook which is fired on a button press (part of a search form).
The hook behaves normally, and is only fired when the button is pressed. However, once I've fired it once, it's then fired every time the component re-renders (usually due to state changes).
So if I search once, then edit the search fields, the results appear immediately, and I don't have to click on the search button again.
Not the UI I want, and it causes an error if you delete the search text entirely (as it's trying to search with null as the variable), is there any way to prevent the useLazyQuery from being refetched on re-render?
This can be worked around using useQuery dependent on a 'searching' state which gets toggled on when I click on the button. However I'd rather see if I can avoid adding complexity to the component.
const AddCardSidebar = props => {
const [searching, toggleSearching] = useState(false);
const [searchParams, setSearchParams] = useState({
name: ''
});
const [searchResults, setSearchResults] = useState([]);
const [selectedCard, setSelectedCard] = useState();
const [searchCardsQuery, searchCardsQueryResponse] = useLazyQuery(SEARCH_CARDS, {
variables: { searchParams },
onCompleted() {
setSearchResults(searchCardsQueryResponse.data.searchCards.cards);
}
});
...
return (
<div>
<h1>AddCardSidebar</h1>
<div>
{searchResults.length !== 0 &&
searchResults.map(result => {
return (
<img
key={result.scryfall_id}
src={result.image_uris.small}
alt={result.name}
onClick={() => setSelectedCard(result.scryfall_id)}
/>
);
})}
</div>
<form>
...
<button type='button' onClick={() => searchCardsQuery()}>
Search
</button>
</form>
...
</div>
);
};
You don't have to use async with the apollo client (you can, it works). But if you want to use useLazyQuery you just have to pass variables on the onClick and not directly on the useLazyQuery call.
With the above example, the solution would be:
function DelayedQuery() {
const [dog, setDog] = useState(null);
const [getDogPhoto] = useLazyQuery(GET_DOG_PHOTO, {
onCompleted: data => setDog(data.dog)
})
return (
<div>
{dog && <img src={dog.displayImage} />}
<button
onClick={() => getDogPhoto({ variables: { breed: 'bulldog' }})}
>
Click me!
</button>
</div>
);
}
The react-apollo documentation doesn't mention whether useLazyQuery should continue to fire the query when variables change, however they do suggest using the useApolloClient hook when you want to manually fire a query. They have an example which matches this use case (clicking a button fires the query).
function DelayedQuery() {
const [dog, setDog] = useState(null);
const client = useApolloClient();
return (
<div>
{dog && <img src={dog.displayImage} />}
<button
onClick={async () => {
const { data } = await client.query({
query: GET_DOG_PHOTO,
variables: { breed: 'bulldog' },
});
setDog(data.dog);
}}
>
Click me!
</button>
</div>
);
}
The Apollo Client documentation isn't explicit about this, but useLazyQuery, like useQuery, fetches from the cache first. If there is no change between queries, it will not refetch using a network call. In order to make a network call each time, you can change the fetchPolicy to network-only or cache-and-network depending on your use case (documentation link to the fetchPolicy options). So with a fetchPolicy change of network-only in your example, it'd look like this:
const AddCardSidebar = props => {
const [searching, toggleSearching] = useState(false);
const [searchParams, setSearchParams] = useState({
name: ''
});
const [searchResults, setSearchResults] = useState([]);
const [selectedCard, setSelectedCard] = useState();
const [searchCardsQuery, searchCardsQueryResponse] =
useLazyQuery(SEARCH_CARDS, {
variables: { searchParams },
fetchPolicy: 'network-only', //<-- only makes network requests
onCompleted() {
setSearchResults(searchCardsQueryResponse.data.searchCards.cards);
}
});
...
return (
<div>
<h1>AddCardSidebar</h1>
<div>
{searchResults.length !== 0 &&
searchResults.map(result => {
return (
<img
key={result.scryfall_id}
src={result.image_uris.small}
alt={result.name}
onClick={() => setSelectedCard(result.scryfall_id)}
/>
);
})}
</div>
<form>
...
<button type='button' onClick={() => searchCardsQuery()}>
Search
</button>
</form>
...
</div>
);
};
When using useLazyQuery, you can set nextFetchPolicy to "standby". This will prevent the query from firing again after the first fetch. For example, I'm using the hook inside of a Cart Context to fetch the cart from an E-Commerce Backend on initial load. After that, all the updates come from mutations of the cart.

How to retrieve an input form value with redux-form and formValueSelector

I am trying to retrieve a single input value and log it. This is an edit form with existing name value prop.
I am not setting state 'name' with field input's value for some reason. I am not sure how to structure the connect part which I think is my problem. In particular, I am not clear on how to write mapStateToProps to include both my non-form state and form state.
partial scaled down code:
import { Field, reduxForm, formValueSelector } from 'redux-form';
import { connect } from 'react-redux';
import { updateStable } from '../../actions/index';
const selector = formValueSelector('myEditForm');
class EditStuff extends Component {
constructor(props) {
super(props);
this.state = {
name: this.props.name
};
}
componentDidMount() {
this.props.initialize({
name: this.props.name || ''
});
}
componentDidUpdate() {
// this.state.name is not getting set from input value
this.props.updateLocalActiveStuffData(this.state.name);
}
render() {
const { handleSubmit } = this.props;
return (
<div>
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<Field
label="Name"
name="name"
class="name"
type="text"
component={renderField}
/>
<button type="submit" className="btn btn-primary">
Submit
</button>
</form>
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
displayEditForm: state.displayEditForm, //my own non-form related state
name: selector(state, 'name') //form input 'name'
};
}
export default connect(mapStateToProps, { updateStuff })(
reduxForm({
form: 'myEditForm'
})(EditStuff)
);
I think if you can't retrieve the value it's because the name of your form in reduxForm and the formValueSelector name are different 'StableEditForm' != 'myEditForm'
You have more info on selectors here
If you want to initialise your form with values, you should set it from your state in mapStateToProps with the initialValues props, something like this:
function mapStateToProps(state) {
return {
initialValues: state.displayEditForm, // value of your form
};
}
A great exemple here
I hope this can help you

In list component, how to implement a component to change the number of results displayed

I was thinking about making a simple component with a Select and the list of results that should be displayed.
After reading the code, that seems impossible, because if I change the url, then update is triggered by componentWillReceiveProps, and this method does not check for a change of perPage
Change the prop perPage of the List component does not work either because the List use this prop only if the query does not already contains perPage
Here is an example of what I want to do :
import { List } from "admin-on-rest";
class SourceList extends Component {
constructor(props) {
super(props);
this.state = {
perPage: 10
};
}
render() {
return (
<div>
<Button
onClick={() => {
this.setState({ perPage: 50 });
}}
/>
<List {...props} perPage={this.state.perPage}>
... Here would be the content of the list
</List>
</div>
);
}
}

how to combine checkbox with text input in reactjs

i am trying to build a Ui component in Reactjs which combines a checkbox and a text input attched to it (instead of a text label) so that if the checkbox is checked , the user can change the text input , and if its unchecked the user will not be able to do so
the final goal is to render outside of the component all of textinputs valus which left checked as a list or as a menu item.
Its should look like this :
Checkbox with Text input
anyone knows how should i do this ? im new to reactjs and got a bit confused how to pass logic between two components(as in here between the checkbox and the text input and between the "combo" component and the outer rendered list) .
thanks in advance !
EDIT1:
well i managed to build the component but i cant make the children call the parent handler (handlerCheckbox , handlerInput)in order to actually make the magic happen.
anything im doing wrong ?
this is the child:
class CheckboxTxtInput extends React.Component{
constructor(props){
super(props);
console.log(props.isChecked)
}
handleCheckboxChild(e) {
this.props.handleCheckbox(e,this.props.id)
}
handleInputChild(e){
this.props.handleInput(e,this.props.id)
}
render(){
return (
<div>
<input type="checkbox" onChange={this.handleCheckboxChild} defaultChecked={this.props.isChecked} />
<input type="text" value={this.props.inputValue} disabled={!this.props.isChecked} onChange={this.handleInputChild}/>
</div>
)
}
}
This is the parent:
export default class Text extends React.Component {
constructor(props) {
super(props);
this.state = {
textItems: [{id:0,inputValue:'text',isChecked:true},{id:1,inputValue:'text',isChecked:true}
,{id:2,inputValue:'text',isChecked:true},{id:3,inputValue:'text',isChecked:true}]
};
this.handleCheckbox = this.handleCheckbox.bind(this);
this.handleInput= this.handleInput.bind(this);
}
handleCheckbox(e,id) {
var stateCopy = Object.assign({}, this.state);
stateCopy.textItems[id].isChecked = e.target.value;
this.setState(stateCopy);
}
handleInput(e,id){
var stateCopy = Object.assign({}, this.state);
stateCopy.textItems[id].text = e.target.value;
this.setState(stateCopy);
}
render () {
return (
<div>
<hr className="divider-long"/>
<UI.sectionDividerLabeled label="Show/Hide Text"/>
<hr className="divider-long"/>
<p>Here you can show\hide your text</p>
<div>
<CheckboxTxtInput id={this.state.textItems[0].id} isChecked={this.state.textItems[0].isChecked}
inputValue={this.state.textItems[0].inputValue} handleInput={this.handleInput}
handleCheckbox={this.handleCheckbox} />
<CheckboxTxtInput id={this.state.textItems[1].id} isChecked={this.state.textItems[1].isChecked}
inputValue={this.state.textItems[1].inputValue} handleInput={this.handleInput}
handleCheckbox={this.handleCheckbox}/>
<CheckboxTxtInput id={this.state.textItems[2].id} isChecked={this.state.textItems[2].isChecked}
inputValue={this.state.textItems[2].inputValue}
handleInput={this.handleInput} handleCheckbox={this.handleCheckbox}/>
<CheckboxTxtInput id={this.state.textItems[3].id} isChecked={this.state.textItems[3].isChecked}
inputValue={this.state.textItems[3].inputValue} handleInput={this.handleInput}
handleCheckbox={this.handleCheckbox}/>
</div>
<RenderText />
</div>
)
}
}
The simplest, React-like way to do this is to have a parent wrapper component - say LabeledCheckbox which contains your Text input and your Checkbox components.
When either of the child components do something, they call a callback provided by the parent, and the parent maintains the state for the two components, passing that state down into the props of both children.
The children in this case would never maintain their own state, instead simply calling callbacks and being prop-fed.
Create one component with checkbox and input field with the state of the checkbox and text field.
And then you can reuse it where you want.
You can do something like this :
class CheckboxTxtInput extends React.Component{
constructor(){
super();
this.state = {
checkbox: false,
inputValue: ""
}
}
handleCheckbox(e){
this.setState({checkbox: e.target.checked})
}
handleInput(e){
this.setState({inputValue: e.target.value})
}
render(){
return (
<div>
<input type="checkbox" onChange={this.handleCheckbox.bind(this)} checked={this.state.checkbox}/>
<input type="text" value={this.state.inputValue} disabled={this.state.checkbox} onChange={this.handleInput.bind(this)}/>
</div>
)
}
}
class Test extends React.Component {
render(){
return (
<div><CheckboxTxtInput /></div>
)
}
}
React.render(<Test />, document.getElementById('container'));
Here is the fiddle.
Hope this helps.

How to render a container dynamically in react-redux?

I am new to redux-react so excuse me if it is a stupid question.
I have a page which shows the list of some products in a table.
When I click on a product, I want to show details about that product in a panel overlaying the main page.
The problem is that the detail page has already a component and container class.
If I want to render the component I have to mix the main page and detail page containers together which I don't want. I want to keep each page component and container separate.
When I render container I get the error
Invariant Violation: Could not find "store" in either the context or props. Either wrap the root component in a , or explicitly pass "store" as a prop.
I don't know how to pass it and I googled about it I couldn't find a solution for my case. I don't want to initialize a new store.
Here is my click function to show the detail page.
onClick(){
ReactDOM.render(
<div>
<div className="my-panel" id="my-panel" data-toggler=".is-active">
<ProductDetailContainer />
<button className="button" data-toggle="my-panel">Close</button>
</div>
</div>,
wrapper
);
}
here is my product detail container code:
export class ProductDetailContainer extends RootContainer {
constructor(props) {
super(props);
this.state = {
productDetail: {}
};
}
componentDidMount() {
this.props.dispatch(someAction);
}
componentWillReceiveProps(nextProps) {
//some code here
}
handleRefresh() {
//some code here
}
render() {
return (
<div className="row small-12 columns">
<ProductDetailComponent
data={this.state.productDetail}
/>
</div>
);
}
}
ProductDetailContainer.propTypes = {
productDetail: PropTypes.object
};
export function mapStateToProps(state) {
return {
productDetail: state.productdetail
};
}
export default connect(mapStateToProps)(ProductDetailContainer);

Resources