I am building a react single page app that will call API to search based on a user's input, and the user has to log in to be able to use the app.
The hierarchy of the components are:
App.js(Handles login and routing)<SearchPage />and<SearchResults />- There are
<SearchNavBar />and<SearchMain />under<SearchPage />(<SearchMain />contains the form and input for users to submit, and after submitting, users should be navigated to the route that<SearchResults />is at.)
Here is the issue:
Since react only allows one direction data flow, I have to pass user's input from <SearchMain /> upwards to App.js, and then save it in the state of App.js, and pass it down as a prop to <SearchResults /> together with the token got from App.js. However, even I have updated the state in App.js, and passed it as a prop to its child component <SearchResults />, while console.logging the props of the child component <SearchResults />, I could only get the initial values of the state, not the updated ones, why??
Code:
App.js:
const myMSALObj = new UserAgentApplication(msalConfig);
class App extends Component {
constructor(props) {
super(props)
this.state = {
res:'',
isLoggedIn:false,
keyword:''
}
this.handleLogOut = this.handleLogOut.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleLogOut(){
myMSALObj.logout();
this.setState({isLoggedIn:false})
}
handleSubmit (typedValue){
this.setState({keyword:typedValue},() => console.log(this.state));//here works just fine, showing correct state value
window.location.replace('/results')
};
async componentDidMount(){
const idToken_response = await myMSALObj.loginPopup(loginRequest);
const response = await myMSALObj.acquireTokenPopup(loginRequest);
this.setState({isLoggedIn:true,res:response});
}
render(){
return (
<Router>
<div>
<Switch>
{this.state.isLoggedIn === true ?
<Route exact path="/" render={() => <SearchPage
parentState = {this.state}
handleLogOut = {this.handleLogOut}
handleSubmit = {this.handleSubmit} />}/>:
<Route exact path="/" render={() => (<div>Please wait till you are logged in...</div>)}/>}
<Route exact path="/results" render={() => <SearchResults
parentState = {this.state.keyword}
handleLogOut = {this.handleLogOut} />}/>
</Switch>
</div>
</Router>
);
}
}
export default App;
SearchPage:
class SeachPage extends Component{
constructor (props){
super (props);
this.state = {
// userInfo: this.props,
accessToken: this.props.parentState.res.accessToken,
name:this.props.parentState.res.account.name,
email:this.props.parentState.res.account.userName
}
// console.log(this.props)
}
render(){
return (
<Fragment>
<SearchNavBar
info={this.state}
handleLogOut = {this.props.handleLogOut}/>
<SearchMain
accessToken={this.state.accessToken }
handleSubmit = {this.props.handleSubmit}/>
</Fragment>
)
}
}
export default SeachPage;
SearchMain:
class SearchMain extends Component{
constructor(props){
super(props);
this.state = {
token:this.props.accessToken,
keyword:'',
typed:'',
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmitForm = this.handleSubmitForm.bind(this);
// console.log(this.props)
}
handleChange(event) {
let typed = event.target.value;
let keyword = event.target.value;
this.setState({typed: typed,keyword:keyword},() => console.log(this.state));
};
handleSubmitForm(event){
const keyword = this.state.keyword;
this.props.handleSubmit(keyword);
event.preventDefault();
}
render(){
return(
<div className="container" id="searchBox">
<form onSubmit = {this.handleSubmitForm } className="field is-grouped">
<p className="control is-expanded">
<input type="text" className="input" value={this.state.keyword} onChange={this.handleChange} autoComplete="off" placeholder="Find an asset" required></input>
</p>
<p className="control">
<button id="button-in-nav-bar" type="submit" className="button is-primary"><strong>Search</strong></button>
</p>
</form>
</div>
)
}
};
export default SearchMain;
SearchResults
class SearchResults extends Component {
constructor(props){
super(props);
console.log(this.props) //here is the issue!! onlyshowing{res:'',isLoggedIn:false,keyword:''}
}
render (){
return(
<div>test results</div>
)
}
};
export default SearchResults;
Update
So briefly, I was able to pass the keyword upwards to the parent component, just seem to be a little late, the screenshots may better describe my struggle:
and if I uncomment the window.location.replace('/results') in the first screenshot, the page will be navigated to '/results', and if I console.log(this.props) in the render function
, the props were displayed 4 times, and the last 2 times had partly correct info.
, still the keyword was missing... I had a feeling that this might bc of the async nature but I really have zero clue on how to fix it.. (side question, why it was desiplayed 4 times?)