How to show Data results on click in React? - javascript

I am trying to show my results from a JSON file only when the search button is clicked. What is the correct way to do it?
Right now as the user types a product the results are show. I have a simple filter, that is filtering the results, but I would like to make that only appear when the button is clicked. I only want to show results when the search button is clicked.
class App extends Component {
constructor(props){
super(props);
this.state = {
value: '',
list: []
}
this.handleChange = this.handleChange.bind(this);
this.handleSearch = this.handleSearch.bind(this);
this.refresh();
}
handleChange(event){
this.setState({ ...this.state, value: event.target.value })
}
refresh(){
axios.get(`${URL}`)
.then(resp => this.setState({...this.state, value: '', list: resp.data}));
}
handleSearch(product){
this.refresh();
}
render(){
return(
<div className="outer-wrapper">
<Header />
<main>
<Container>
<Row>
<Col xs={12} md={12} lg={12} className="pl-0 pr-0">
<SearchBar
handleChange={this.handleChange}
handleToggle={this.handleToggle}
handleSearch={this.handleSearch}
value={this.state.value}
/>
<SearchResultBar
value={this.state.value}
/>
<Filter />
</Col>
</Row>
<ProductList
value={this.state.value}
list={this.state.list}
/>
</Container>
</main>
</div>
)
}
}
export default App;
class Search extends Component {
constructor(props){
super(props);
}
render(){
return(
<div className="search-input">
<InputGroup>
<Input placeholder='Enter Search'
onChange={this.props.handleChange}
value={this.props.value}
/>
<InputGroupAddon className='input-group-append'
onClick={this.props.handleSearch}>
<span className='input-group-text'>
<i className="fa fa-search fa-lg fa-flip-horizontal"></i>
</span>
</InputGroupAddon>
</InputGroup>
</div>
)
}
}
export default Search;
class ProductList extends Component {
constructor(props){
super(props);
this.state = {
}
}
render(){
let filteredSearch = this.props.list.filter(
(product) => {
return product.title.indexOf(this.props.value) !== -1
}
)
return(
<Container>
<Row>
{
filteredSearch.map(item => {
return <Product {...item} key={item._id} />
})
}
</Row>
</Container>
);
}
}
export default ProductList;
As it stands, my list of products is being displayed in the app as soon as it loads. This seems something trivial, but I have been scratching my head in trying to solve it.

You're calling this.refresh() inside the constructor. So it gets run on mount.
Just remove it from the constructor and you should be fine.

Related

React Class Component is not changing with the change of its props [duplicate]

react-router-dom v5 and React 16
My loading app component contains:
ReactDOM.render(
<FirebaseContext.Provider value={new Firebase()}>
<BrowserRouter>
<StartApp />
</BrowserRouter>,
</FirebaseContext.Provider>,
document.getElementById("root")
);
I have a route component which contains:
{
path: "/member/:memberId",
component: MemberForm,
layout: "/admin"
},
Admin component:
return (
<>
<div className="main-content" ref="mainContent">
<LoadingComponent loading={this.props.authState.loading}>
<AdminNavbar
{...this.props}
brandText={this.getBrandText(this.props.location.pathname)}
/>
<AuthDetailsProvider>
<Switch>{this.getRoutes(routes)}</Switch>
</AuthDetailsProvider>
<Container fluid>
<AdminFooter />
</Container>
</LoadingComponent>
</div>
</>
)
this.getRoutes in the Switch contains the reference route above.
Now from one of my component pages I can navigate to /member/{memberid} this works fine.
the route loads a component called MemberForm
inside MemberForm I have a row that contains this method:
<Row>
{ this.displayHouseholdMembers() }
</Row>
displayHouseholdMembers = () => {
const householdDetails = this.state.family;
if (householdDetails) {
return householdDetails.map((key, ind) => {
if (key['uid'] != this.state.memberKeyID) {
return (
<Row key={ind} style={{ paddingLeft: '25px', width: '50%'}}>
<Col xs="5">
<Link to={ key['uid'] }>
{ key['first'] + " " + key['last'] }
</Link>
</Col>
<Col xs="4">
{ key['relation'] }
</Col>
<Col xs="3">
<Button
color="primary"
size="sm"
onClick={(e) => this.removeHouseRelation(key)}
>
Remove
</Button>
</Col>
</Row>
);
}
});
}
};
MemberForm:
in componentDidMount I do an firebase call to check for the data pertaining to the user using the uid aka memberId in the URL.
class MemberForm extends React.Component {
constructor(props) {
super(props);
this.state = {
...INITIAL_STATE,
currentOrganization: this.props.orgID,
householdRelation: ['Spouse', 'Child', 'Parent', 'Sibling'],
householdSelected: false,
};
}
componentDidMount() {
let urlPath, personId;
urlPath = "members";
personId = this.props.match.params.memberId;
// if it is a member set to active
this.setState({ statusSelected: "Active" })
this.setState({ memberSaved: true, indiUid: personId });
// this sets visitor date for db
const setVisitorDate = this.readableHumanDate(new Date());
this.setState({ formType: urlPath, visitorDate: setVisitorDate }, () => {
if (personId) {
this.setState({ memberSaved: true, indiUid: personId });
this.getIndividualMemberInDB(
this.state.currentOrganization,
personId,
this.state.formType,
INITIAL_STATE
);
}
});
}
...
return (
<>
<UserHeader first={s.first} last={s.last} />
{/* Page content */}
<Container className="mt--7" fluid>
<Row>
...
<Row>
{ this.displayHouseholdMembers() }
</Row>
</Form>
</CardBody>
) : null}
</Card>
</Col>
</Row>
<Row>
<Col lg="12" style={{ padding: "20px" }}>
<Button
color="primary"
onClick={e => this.submitMember(e)}
size="md"
>
Save Profile
</Button>
{ this.state.indiUid ? (
<Button
color="secondary"
onClick={e => this.disableProfile()}
size="md"
>
Disable Profile
</Button>
) : null }
</Col>
</Row>
</Container>
</>
);
When I click on the Link it shows the url has changed 'members/{new UID appears here}' but the page does not reload. I believe what's going on is that since it's using the same route in essence: path: "/member/:memberId"it doesn't reload the page. How can I get it to go to the same route but with the different memberId?
You are correct that the MemberForm component remains mounted by the router/route when only the path param is updating. Because of this the MailForm component needs to handle prop values changing and re-run any logic depending on the prop value. The componentDidUpdate is the lifecycle method to be used for this.
Abstract the logic into a utility function that can be called from both componentDidMount and componentDidUpdate.
Example:
getData = () => {
const urlPath = "members";
const { memberId } = this.props.match.params;
// this sets visitor date for db
const setVisitorDate = this.readableHumanDate(new Date());
this.setState(
{
// if it is a member set to active
statusSelected: "Active",
memberSaved: true,
indiUid: memberId,
formType: urlPath,
visitorDate: setVisitorDate
},
() => {
if (memberId) {
this.setState({ memberSaved: true, indiUid: memberId });
this.getIndividualMemberInDB(
this.state.currentOrganization,
memberId,
this.state.formType,
INITIAL_STATE
);
}
}
);
}
The lifecycle methods:
componentDidMount() {
this.getData();
}
componentDidUpdate(prevProps) {
if (prevProps.match.params.memberId !== this.props.match.params.memberId) {
this.getData();
}
}
For react-router-dom v6, can you try with simple routing? Create a Test.js with
const Test = ()=> <h1>Test Page</h1>
Then, create a Home.js with
const Home = ()=> <Link to="/test">Test</Link>
Then, add them to route.
<BrowserRouter>
<Routes>
<Route path="/" element={<Home/>} />
<Route path="/test" element={<Test />} />
</Routes>
</BrowserRouter>
Does your component structure look like this? For index route, look more at https://reactrouter.com/docs/en/v6/getting-started/overview.

React: add dynamically fields

I have a class in which I should add dynamically fields when the user click on "Add Button" or delete the fields if the user click on the "Remove Button".
export default class myClass extends Component
{
constructor(props){
super(props);
}
Fields = [
{
name: '$ClassName',
fields: [
{
name: 'ClassName.FirstField',
col: ["lg-6", 'md-6', 'sm-12'],
required: true,
label: "First Field"
}
]
},
]
render()
{
const self = this
return(
<Column col={'12'} className="mt-2 mb-2">
<Row>
{
this.Fields.map((group, i) => (
<Column key={`${group.name}_i`} col="12" className="mt-4 mb-2 form-fields-wrap">
<h5 className="form-section-title">{group.label}</h5>
<Row>
{
Start.renderFieldsGroup(group.fields, group.name, this.props.preview)
}
</Row>
</Column>
))
}
</Row>
</Column>
)
}
Now I should create the possibility to add (and remove) the Fields array when an user click on Add Button (or Remove Button).
How can I do to add dynamically add this field?
EDIT:
export default class myClass extends Component
{
constructor(props){
super(props);
this.state = { inputs: ['input-0'] };
}
tryFunction(){
const self = this
return(
<Column col={'12'} className="mt-2 mb-2">
<Row>
{
this.Fields.map((group, i) => (
<Column key={`${group.name}_i`} col="12" className="mt-4 mb-2 form-fields-wrap">
<h5 className="form-section-title">{group.label}</h5>
<Row>
{
Start.renderFieldsGroup(group.fields, group.name, this.props.preview)
}
</Row>
</Column>
))
}
</Row>
</Column>
)
}
appendInput() {
console.log("11111")
var newInput = `input-${this.state.inputs.length}`;
this.setState(prevState => ({ inputs: prevState.inputs.concat([newInput]) }));
}
render()
{
const self = this
return(
<div>
<div id="dynamicInput">
{console.log("this.state.input ", this.state.input)}
{this.state.inputs.map(input => this.tryFunction())}
</div>
<button onClick={ () => this.appendInput() }>
CLICK ME TO ADD AN INPUT
</button>
</div>
);
}
You call this.Fields.map() in your edit but as far as i can tell you dont actually declare Fields. I made an example how i would solve such a situation, you should be able to use the same technique for your situation.
export default class MyClass extends React.Component{
constructor(props){
super(props);
this.state = {
dynamicItems: []
};
}
handleClick(){
//Add a new item component to state.dynamicItems
this.setState(prevState => ({
dynamicItems: [...prevState.dynamicItems, <Item text="text" key="key" />]
}));
}
render(){
return(
<div className="page">
<div className="dynamic-container">
{/*Render item components*/}
{this.state.dynamicItems.map((item) => {return item})}
</div>
<button onclick={() => this.handleClick()}>Add Item</button>
</div>
);
}
}
//Item component
class Item extends React.Component{
render(){
return(
<div className="item" key={this.props.key}>
<p>{this.props.text}</p>
</div>
);
}
}

React - How to Populate one Dropdowns based on selection from another Dropdown by Passing State as props

I am creating a bar with two dropdown. The second dropdown depends of the selection from the first dropdown. I have 3 Components :
1. Dropdown Bar : Contains FirstDropdown and Second Dropdown
2. FirstDropdown
3. SecondDropdown
Trying to pass State -> Practice that appears in the FirstDropdown Component as props to SecondDropdown Component. Clearly I'm not doing this correctly. Any Help will be appreciate. Thank you in advance!
class DropdownBar extends React.Component {
constructor(props) {
super(props);
}
render () {
return (
<div>
<div className="top-bar">
<Row>
<div style={{marginTop: 15, marginBottom:15}}>
<Col span={8}><FirstDropdown practice={this.props.practice} /></Col>
<Col span={8}><SecondDropdown /></Col>
</div>
</Row>
</div>
</div>
)
}
class FirstDropdown extends React.Component {
constructor() {
super();
this.state = {
practices: [
name = 'Jon',
name = 'potato',
name = 'stark',
],
practice: 'stark'
}
}
onChangePractice(value) {
console.log(`selected ${value}`);
this.setState({
practice: value
})
}
render () {
const {practices} = this.state
return (
<div>
<Row>
<div className="First-dropdown">
<Col span={8}><div className="dropdown-title">Research: </div></Col>
<Col span={14}>
<Select
showSearch
style={{ width: '100%' }}
placeholder="Select a Something"
optionFilterProp="children"
onChange={this.onChangePractice.bind(this)}
onFocus={onFocus}
onBlur={onBlur}
onSearch={onSearch}
filterOption={(input, option) =>
option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
{practices.map(practice => (
<Option
value={practice}
key={practice}
data-automation={practice.name}
>{practice}</Option>
))}
</Select>
</Col>
</div>
</Row>
</div>
)
}
class SecondDropdown extends React.Component {
constructor(props) {
super(props);
this.state = {
modules: [
name = 'Drogon',
name = 'Rhaegal',
name = 'Viserion',
]
}
}
componentDidUpdate(prevProps) {
console.log(this.props.practice)
if (!equal(this.props.practice, prevProps.practice))
{
this.updatePractice();
}
}
render () {
const {modules} = this.state
console.log(this.props.practice )
let practice = this.props.practice;
if (practice === 'stark') {
return (
<div>
<Row>
<div className="benchmark-dropdown">
<Col span={4}><div className="dropdown-title">Module: </div></Col>
<Col span={16}>
<Select
showSearch
style={{ width: '100%' }}
placeholder="Select Something"
optionFilterProp="children"
onChange={onChange}
onFocus={onFocus}
onBlur={onBlur}
onSearch={onSearch}
filterOption={(input, option) =>
option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
{modules.map(item => (
<Option
value={item}
key={item}
>{item}</Option>
))}
</Select>
</Col>
</div>
</Row>
</div>
)
} else {
return <div> NOOOOO </div>
}
}
In order for both dropdowns to have access to the practice prop, you need to lift it up to the DropdownBar's state, and pass down both practice and a way to update practice.
class DropdownBar extends Component {
state = {
practice: '',
}
handlePracticeChange = (value) => {
setState({ practice: value });
}
render() {
return (
<div>
<FirstDropdown
practice={this.state.practice}
onPracticeChange={this.handlePracticeChange}
/>
<SecondDropdown practice={this.state.practice} />
</div>
)
}
}
So, practice only lives in DropdownBar, and the practices array should live in FirstDropdown child.
In FirstDropdown, you should pass props.onPracticeChange to your Select's onChange:
class FirstDropdown extends Component {
render() {
...
<Select
onChange={this.props.onPracticeChange}
...
}
}
From your code example, it looks like Select passes the currently selected value to onChange.
I'd pull the state into the parent.
class MainBar extends React.Component {
state = {
practice: null
};
handleChange = practice => {
this.setState({ practice });
}
render() {
return (
<div className="top-bar">
<Row>
<div style={{marginTop: 15, marginBottom:15}}>
<Col span={8}>
<FirstDropdown
onChange={this.handleChange}
practice={this.state.practice}
/>
</Col>
<Col span={8}>
<SecondDropdown practice={this.state.practice} />
</Col>
</div>
</Row>
</div>
);
}
}

ReactJS component doesn't re-render

I have a following example simple page:
App.js:
export default class App extends React.Component {
render() {
return <Router>
<Switch>
<Route exact path='/' component={ArticlesPage}/>
<Route path='/search' component={SearchPage}/>
</Switch>
</Router>
};
};
ArticlesPage.js:
export default class ArticlesPage extends React.Component {
constructor(props) {
super(props);
}
render() {
return <Grid>
<Row>
<Col lg={12}>
<SearchBox/>
</Col>
</Row>
<Row>
<Col lg={12}>
articles
</Col>
</Row>
</Grid>;
}
};
SearchPage.js:
export default class SearchPage extends React.Component {
constructor(props) {
super(props);
const {q} = queryString.parse(location.search);
this.state = {
query: q
};
}
render() {
return <Grid>
<Row>
<Col lg={12}>
<SearchBox/>
</Col>
</Row>
<Row>
<Col lg={12}>
search {this.state.query}
</Col>
</Row>
</Grid>;
}
};
SearchBox.js:
export default class SearchBox extends React.Component {
constructor(props) {
super(props);
this.state = {
q: ''
};
}
onFormSubmit = (e) => {
e.preventDefault();
const {router} = this.context;
router.history.push('/search?q=' + this.state.q);
};
handleChange = (e) => {
this.setState({q: e.target.value});
};
render() {
return <form onSubmit={this.onFormSubmit}>
<Col lg={10} lgOffset={1}>
<FormGroup>
<input type="text" name="q" id="q" ref={i => this.searchInput = i} onChange={this.handleChange} />
</FormGroup>
</Col>
</form>;
}
};
And now, when I'm on the index page and type something in the input next send form, React render SearchPage.js and return correctly text search *and what I typed*, try again type something else in the input and send form, and React still show my previous text (not rerender).
What can be wrong with this simple page?
You have two different state variables, query on <SearchPage /> and q on <SearchBox />. What you are changing is q, but the variable you are rendering as text is query.
You need to lift state up and pass query as prop to <SearchPage />.
Here's why the text on SearchPage doesn't update: the constructor runs once and updates the variable in state, but when the app re-renders, React, wanting to be efficient, sees that it would re-render a new SearchPage in the same spot as the previous one, so instead of replacing it, it keeps the state of the old one. Because of this, SearchPage's state still keeps the old q variable.
Here's how you can fix it: make your SearchPage accept the search query as a prop, and render that.
class SearchPage extends React.Component {
render() {
return (
<Grid>
<Row>
<Col lg={12}>
<SearchBox />
</Col>
</Row>
<Row>
<Col lg={12}>search {this.props.query}</Col>
</Row>
</Grid>
)
}
}
In the parent, where the route for it is being rendered, use a render function, take the props of it, parse the actual query from props.location.search, and pass it directly to SearchPage.
<Route
path="/search"
render={props => <SearchPage query={getSearchQuery(props.location.search)} />}
/>
// utility function to keep things clean
function getSearchQuery(locationSearch) {
return queryString.parse(locationSearch.slice(1)).q
}
Here's a working demo.

React - Change state in child component

I trying to change my state in another component. I have passed the state by props
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.state = {
isOpen: false
};
}
<MobileContent isOpen={this.state.isOpen} />
In my MobileContent component i want to change the state when i click on the element.
class MobileContent extends Component {
render() {
if (this.props.isOpen) {
return (
<Grid fluid>
<div className="mobileContent">
<Row center="xs">
<Col xs={12}>
<span className="button">Hello, world!</span>
<span className="close" onClick={this.handleClick} >X</span>
</Col>
</Row>
</div>
</Grid>
);
}
return null;
}
}
export default MobileContent;
Thanks for the help !!
If you want the Child component to notify the parent, then you should pass an additional prop down to the child which is a function. The child can then call that. So in the parent:
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.state = {
isOpen: false
};
}
<MobileContent isOpen={this.state.isOpen} onClose={this.handleClick}/>
And in the child:
render() {
if (this.props.isOpen) {
return (
<Grid fluid>
<div className="mobileContent">
<Row center="xs">
<Col xs={12}>
<span className="button">Hello, world!</span>
<span className="close" onClick={this.props.onClose}>X</span>
</Col>
</Row>
</div>
</Grid>
);
}
return null;
}

Categories