-1

*edit to provide solution in comments

I have an app that renders 2 components, a SearchBar form and a Table of data. After the app mounts, an api call is made and setState is called, which triggers a render of the Table. It works fine.

The problem comes from the SearchBar component. On submission, the functional argument handleSubmit is called to make an api request and set the new state. SetState should trigger a render but it doesn't. The state is verified and accurate but there is no render.

Here is my code.

App.js

class App extends Component {
    constructor(props) {
        console.log('app constructor')
        super(props)
        this.state = {
            items: [],
        }
    }

    render() {
        console.log('app render')
        return (
            <div>
                <SearchBar onSubmit={this.handleSubmit} />
                <Table data={this.state.items} />
            </div>
        )
    }

    componentDidMount() {
        console.log('app mounted')
        fetch('/api/items/?search=initial search')
        .then(res => res.json())
        .then((data) => {
            this.setState({
                items: data
            })
            console.log('state post mount ' + this.state.items.length)
        })
    }

    handleSubmit(e) {
        e.preventDefault()
        console.log('search bar submitted ' + e.target.elements.searchBar.value)
        fetch(`/api/items/?search=${e.target.elements.searchBar.value}`)
        .then(res => res.json())
        .then((data) => {
            this.setState({
                items: data
            })
            console.log('state post submit ' + this.state.items[0].name)
        })
    }
}

SearchBar.js

export default class SearchBar extends Component {
    constructor(props) {
        console.log('search bar constructor')
        super(props)
        this.onChange = this.handleChange.bind(this)
        this.onSubmit = this.props.onSubmit.bind(this)
        this.state = {
            value: ''
        }
    }

    handleChange(e) {
        console.log('search bar changed ' + e.target.value)
        this.setState({
            searchBarValue: e.target.value
        })

    }

    render() {
        return (
            <form className='form' id='searchForm' onSubmit={this.onSubmit}>
                <input type='text' className='input' id='searchBar' placeholder='Item, Boss, or Zone' onChange={this.onChange} />
            </form>
        )
    }
}

Table.js

export default class Table extends Component {
    render() {
        if (this.props.data.length === 0) {
            return (
                <p>Nothing to show</p>
            )
        } else {
            return (
                <div className="column">
                    <h2 className="subtitle">
                        Showing <strong>{this.props.data.length} items</strong>
                    </h2>
                    <table className="table is-striped">
                        <thead>
                            <tr>
                                {Object.entries(this.props.data[0]).map(el => <th key={key(el)}>{el[0]}</th>)}
                            </tr>
                        </thead>
                        <tbody>
                            {this.props.data.map(el => (
                                <tr key={el.id}>
                                    {Object.entries(el).map(el => <td key={key(el)}>{el[1]}</td>)}
                                </tr>
                            ))}
                        </tbody>
                    </table>
                </div>
            )
        }
    }
}
undercoverincel
  • 89
  • 1
  • 1
  • 4

3 Answers3

0

Please set this in a variable, when function initiate:-

handleSubmit(e) {
        let formthis=this;
        e.preventDefault()
        console.log('search bar submitted ' + e.target.elements.searchBar.value)
        fetch(`/api/items/?search=${e.target.elements.searchBar.value}`)
        .then(res => res.json())
        .then((data) => {
            formthis.setState({
                items: data
            })
            console.log('state post submit ' + formthis.state.items[0].name)
        })
    }
raman mathur
  • 124
  • 4
0

AS I said in the comment, Remove this line this.onSubmit = this.props.onSubmit.bind(this) from the SearchBar component and replace this one

<form className='form' id='searchForm' onSubmit={this.onSubmit}>

with

<form className='form' id='searchForm' onSubmit={this.props.onSubmit}>

The problem is when you call bind the onSubmit from the props with the this as you did it is using the context of the SearchBar and not the parent so it sets the response to the state of the Search bar and not the App component which you want that way your items state of the parent component never changes an as such you don't get a re-render

harisu
  • 1,376
  • 8
  • 17
  • NICE! Thanks, harisu. I changed the form declaration as you suggested, and added a bind statement in the constructor of the parent component. It now works flawlessly. – undercoverincel Nov 02 '19 at 12:43
  • If you don't mind you should mark this response as correct. – harisu Nov 03 '19 at 13:28
0

Here is the relevant code for my solution. As harisu suggested, I changed the declaration of the form component. I also added a bind statement in the constructor of the parent.

App.js

class App extends Component {
    constructor(props) {
        console.log('app constructor')
        super(props)
        this.handleSubmit = this.handleSubmit.bind(this)
        this.state = {
            items: [],
        }
    }

    handleSubmit(e) {
        e.preventDefault()
        console.log('search bar submitted ' + e.target.elements.searchBar.value)
        fetch(`/api/items/?search=${e.target.elements.searchBar.value}`)
        .then(res => res.json())
        .then((data) => {
            this.setState({
                items: data
            })
        })
        console.log('state post submit ' + this.state.items[0].name)
    }
}

SearchBar.js

export default class SearchBar extends Component {
    render() {
        return (
            <form className='form' id='searchForm' onSubmit={this.props.onSubmit}>
                <input type='text' className='input' id='searchBar' placeholder='Item, Boss, or Zone' onChange={this.onChange} />
            </form>
        )
    }
}
undercoverincel
  • 89
  • 1
  • 1
  • 4