Why is the function of setting the side draw to false ignored when I can get the exact same function to work on click? I can even get the componentDidMount to console.log what is happening? I have been trying different ways to get it to work componentWillunmount etc but nothing other than onclick seems to work with it any thoughts?
export class App extends Component {
state = {
sideDrawerOpen: false
};
drawerToggleClickHandler = () => {
this.setState((prevState) => {
return { sideDrawerOpen: !prevState.sideDrawerOpen };
});
};
sidedrawerToggleClickHandler = () => {
this.setState({ sideDrawerOpen: false });
}
backdropClickHandler = () => {
this.setState({ sideDrawerOpen: false });
};
componentDidMount() {
this.setState({ sideDrawerOpen: false });
};
render() {
let backdrop;
if (this.state.sideDrawerOpen) {
backdrop = <Backdrop click={this.backdropClickHandler} />
}
return (
<div className="App_margin">
<Router>
<div className='App'>
<Nav drawerClickHandler={this.drawerToggleClickHandler} />
<SideDrawer sidedrawerClickHandler={this.sidedrawerToggleClickHandler} show={this.state.sideDrawerOpen} />
{ backdrop }
< Switch >
<Route path='/setup_page' component={setup_page} exact />
<Route path='/main_page' component={main_page} />
<Route path='/settings_page' component={settings_page} />
<Route component={Error} />
</Switch>
</div>
</Router>
</div>
);
}
}
Your state is initializing with state = { sideDrawerOpen: false }. When you run the componentDidMount() function you are trying to set this.state.sideDrawerOpen to false. The state is not changing, therefore there are no updates.
Since you are already initializing the sideDrawerOpen property to false, there is no need to set it to false again upon component mount.
Your componetsDidMount() is completely useless and your code is OK if you remove it.
Related
I have a React application which makes a couple of API requests in a couple of components. I'm trying to modify my code so that rather than these components making new requests on componentDidMount() instead they use data that has been made available in props. I have this working for one component but not the other - as far as I can see there's no difference in who I'm handling these components so I think my method must be at fault.
The function which makes the API call is in app.js below. The result of the call is saved to state (wantedCards or ownedCards depending on the API call) and then passed to the component as a prop in React Router (BrowserRouter)
class App extends Component {
state = {
username: "",
ownedCards: "",
wantedCards: "",
data: "null",
loading: false,
error: false,
};
loadCardData = (props) => {
let path = props.path.split("/");
console.log("NEW PATH2 IS " + path[2]);
if (path[2] == "own" && props.ownedCards.length < 1) {
console.log("OWN TRUE");
var url = `https://apicall/getOwnedCards?user=${path[1]}`;
return axios
.get(`${url}`)
.then((result) => {
this.setState({
data: result.data,
ownedCards: result.data,
loading: false,
error: false,
});
})
.catch((error) => {
console.error("error: ", error);
this.setState({
error: `${error}`,
loading: false,
});
});
} else if (path[2] == "want" && props.wantedCards.length < 1) {
console.log("WANT TRUE");
var url = `https://webhooks.mongodb-realm.com/api/client/v2.0/app/cards-fvyrn/service/Cards/incoming_webhook/getWantedCards?user=${path[1]}`;
return axios
.get(`${url}`)
.then((result) => {
this.setState({
data: result.data,
wantedCards: result.data,
loading: false,
error: false,
});
})
.catch((error) => {
console.error("error: ", error);
this.setState({
error: `${error}`,
loading: false,
});
});
} else {
return;
}
render(props) {
return (
<div className="App">
<BrowserRouter path="foo">
<Navigation />
<Switch>
<Route
exact
path="/:username"
render={(props) => (
<Redirect to={`/${props.match.params.username}/own`} />
)}
/>
<Route
exact
path="/:username/own"
render={(props) => (
<NewTable
status="Own"
p1={props.location.pathname.split("/")[1]}
p2={props.location.pathname.split("/")[2]}
p3={props.location.pathname.split("/")[3]}
p4={props.location.pathname.split("/")[4]}
data={this.state.data}
ownedCards={this.state.ownedCards}
loadCardData={this.loadCardData}
path={props.location.pathname}
/>
)}
/>
<Route
exact
path="/:username/own/team/:id"
render={(props) => (
<CardColumns
p1={props.location.pathname.split("/")[1]}
p2={props.location.pathname.split("/")[2]}
p3={props.location.pathname.split("/")[3]}
p4={props.location.pathname.split("/")[4]}
status="Own"
ownedCards={this.state.ownedCards}
data={this.state.data}
loadCardData={this.loadCardData}
path={props.location.pathname}
/>
)}
/>
<Route
exact
path="/:username/want"
render={(props) => (
<NewTable
data={this.state.data}
wantedCards={this.state.wantedCards}
loadCardData={this.loadCardData}
p1={props.location.pathname.split("/")[1]}
p2={props.location.pathname.split("/")[2]}
p3={props.location.pathname.split("/")[3]}
p4={props.location.pathname.split("/")[4]}
status="Want"
path={props.location.pathname}
/>
)}
/>
<Route
exact
path="/:username/want/team/:id"
render={(props) => (
<CardColumns
p1={props.location.pathname.split("/")[1]}
p2={props.location.pathname.split("/")[2]}
p3={props.location.pathname.split("/")[3]}
p4={props.location.pathname.split("/")[4]}
status="Want"
wantedCards={this.state.wantedCards}
data={this.state.data}
loadCardData={this.loadCardData}
path={props.location.pathname}
/>
)}
/>
<Route
render={function () {
return <p>Not found</p>;
}}
/>
</Switch>
</BrowserRouter>
</div>
);
}
}
Then in my NewTable and CardColumns components I call the loadCardData function in componentDidMount() passing in components' props.
componentDidMount() {
this.props.loadCardData(this.props);
}
When I first go to the path="/:username/own" route the API request is made:
and app.js state is updated:
and the NewTable component props are set:
However when I then go to the path="/:username/own/team/:id" route the api is called again
and console.log reports OWN TRUE which tells me that
if (path[2] == "own" && props.ownedCards.length < 1) {
console.log("OWN TRUE");
....
.... including axios call
}
is being met (i.e. props.ownedCards is empty). However if I look at the component I can see props are set:
However if I then navigate back to the path="/:username/own" route no API call is made and the app makes use of the data in props.
Can anyone advise what the correct way is to makes sure that the components use the data in props rather than keep going back to the API?
The fundamental problem here was that in my table component I was using an to link to other pages and this forced components to remount.
Changing this to <Link to= meant that state was not lost (which was passed down to child components via context api) and I could stop making unneccesary calls!
I have a query.. I need to pass the value from one component to other component.. I want the value received (view console) after click event in the breedlist.js component to replace the base url value "akita" value in the breedimages.js
Refer to:
https://stackblitz.com/edit/react-kfwzgi
First and foremost, you will need to define additional props on both < <BreedImages /> and <BreedList /> components to handle the receiving of selected breeds, and selecting/clicking of breeds, respectively.
On your index.js,
const BASE_URL= "https://dog.ceo/api/breeds/list/all"
class App extends Component {
state={
breeds: [],
selectedBreed: undefined,
}
componentDidMount(){
fetch(BASE_URL)
.then(res => res.json())
.then(data => {
this.setState({
breeds: Object.keys(data.message)
})
})
}
handleSelect(selectedBreed) {
this.setState({ selectedBreed });
}
render() {
const { selectedBreed } = this.state;
return (
<Router>
<div className="App">
<Route path="/" exact strict render={
()=> {
return (<BreedList handleSelect={(val) => this.handleSelect(val)}/>)
}
}/>
<Route path="/images/" exact strict render={
()=> {
return (<BreedImages breed={selectedBreed}/>)
}
}/>
</div>
</Router>
);
}
}
Then, on your BreedList.js, you will need to modify the clickHandler method such that it is calling the handleSelect props method, to pass the selected dog breed back to the parent index.js component,
clickHandler(e) {
const val = e.currentTarget.value
this.props.handleSelect(val)
}
Then, on your BreedImages.js, you can listen to the prop updates on the componentDidUpdate lifecycle hook, and carry out the necessary operations from there.
I added an if statement to compare the prop values before doing any additional operations, as this will prevent any unnecessary re-rendering or requests.
componentDidUpdate(props, prevProps){
if (props.breed !== prevProps.breed) {
console.log(props);
// handle the rest
}
}
I have forked your demo and made the changes over here.
Why the component doesn't update the props when changing the route param?
Trying to use setState inside componentWillReceiveProps but it doesn't even fire?
export default class HomePage extends React.PureComponent {
movies = require('../../movieList.json');
state = {
match: this.props.match,
id: null
}
componentWillReceiveProps(np) {
if(np.match.params.id !== this.props.match.params.id) {
console.log('UPDATING PROPS')
this.setState({ match: np.match })
}
}
render() {
const { match } = this.props;
const movie = this.movies[match.params.id || movieOrder[0]];
const nextMovie = getNextMovie(match.params.id || movieOrder[0]);
const prevMovie = getPrevMovie(match.params.id || movieOrder[0]);
return (
<>
<h1>{movie.title}</h1>
<Link to={nextMovie}>Next</Link>
<Link to={prevMovie}>Prev</Link>
</>
);
}
}
nextMovie and prevMovie get the id which should be set inside link. Unfortunatelly it sets only during 1st render. When clicked I can see the url change, but no updates are fired
Here is the component holding the switch
export default class App extends React.PureComponent {
state = {
isLoading: true,
}
componentDidMount() {
PreloaderService.preload('core').then(() => {
console.log('LOADED ALL');
console.log('PRELOADER SERVICE', PreloaderService);
this.setState({ isLoading: false });
console.log('Preloader assets', PreloaderService.getAsset('dog-video'))
});
}
render() {
const { isLoading } = this.state;
if(isLoading) {
return(
<div>
is loading
</div>
)
}
return (
<div>
{/* <Background /> */}
<Switch>
<Route exact path="/" component={HomePage} />
<Route exact path="/:id" component={props => <HomePage {...props} />} />
<Route component={NotFoundPage} />
</Switch>
<GlobalStyle />
</div>
);
}
}```
Seems to be working with my codesandbox recreation.
I had to improvise a bit with the missing code:
What's the value of movieOrder[0]? Is 0 a valid value for match.params.id?
How do getNextMovie and prevMovie look like?
Also, you're not using the Home component's state in your render logic, so I think, it should render the correct movie even without them.
There are several things off here. componentWillReceiveProps is considered unsafe and is deprecated. Try using componentDidUpdate instead. Also your class component needs a constructor. As it is now state is defined as a static variable.
constructor(props) {
super(props);
this.state = {
match: this.props.match,
id: null
}
}
componentDidUpdate(np) {
if(np.match.params.id !== this.props.match.params.id) {
console.log('UPDATING PROPS')
this.setState({ match: np.match })
}
}
Last, double check to make sure your logic is correct. Right now the Next and Prev buttons link to the same thing:
const nextMovie = getNextMovie(match.params.id || movieOrder[0]);
const prevMovie = getPrevMovie(match.params.id || movieOrder[0]);
<Link to={nextMovie}>Next</Link>
<Link to={prevMovie}>Prev</Link>
As an alternative, if you want to stick with componentWillReceiveProps you need to unbind the current movie, and rebind it with a new one. Here is a code example doing the exact same thing with users.
In my App.js, I'm creating a Route as such:
<Route
exact path='/tools/ndalink'
render={(props) => (
<React.Fragment>
<Header />
<LinkPage {...props} brokerID={this.state.brokerID}></LinkPage>
</React.Fragment>
)}
/>
state.brokerID is initially "", but changed shortly after, therefore LinkPage receives this.state.brokerID as "".
How can I pass the changed state.brokerID (without using Redux)?
You need to use a lifecycle method to get the props to the component to wait for the props called componentDidUpdate.
That being said, you only have to use this if you plan to mutate the brokerId.
Since the process is async you'll have to wait for the props to be passed down. Until the you can show a loading text or progess bar.
class LinkPage extends React.Component {
state = {
builderId: ''
};
componentDidUpdate(prevProps) {
if(this.props.builderId !== prevProps.brokerId) {
this.setState({ builderId: this.props.brokerId });
}
}
render() {
return (
<div className="App">
<h1>{ this.state.builderId ? this.state.builderId : 'Loading' }</h1>
</div>
);
}
}
Or, a simple method would be to not use the lifecycle method at all, Change the following line in render and it should work:
<h1>{ this.props.builderId ? this.props.builderId : 'Loading' }</h1>
If you need to use this brokerId for an api call or something, you can use the setState callback. This would go something like this in the componentDidUpdate lifecycle method.
componentDidUpdate(prevProps) {
if(this.props.builderId !== prevProps.builderId) {
this.setState({ builderId: this.props.builderId }, () => {
//use this.state.brokerIdhere
});
A button click shall filter my job-card array to only one category. E.g. button "Marketing" should filter to those jobs from array who have prop "jobstags: Marketing". I used a very similar procedure like for my input which filters jobs perfectly.
I can console log my event (the button click) with the according value ("Marketing"). But it still doesn't filter correctly...
In my app I did this:
export default class App extends Component {
state = {
jobs: jobs,
searchfield: '',
jobtags: ''
}
onSearchChange = event => {
this.setState({ searchfield: event.target.value })
}
onClickChange = event => {
console.log(event.target.value)
this.setState({ jobtags: event.target.value })
}
render() {
const filteredJobs = this.state.jobs.filter(job => {
return (
job.position
.toLowerCase()
.includes(this.state.searchfield.toLowerCase()) ||
job.company
.toLowerCase()
.includes(this.state.searchfield.toLowerCase()) ||
job.jobtags.toLowerCase().includes(this.state.jobtags.toLowerCase())
)
})
// this.save()
if (this.state.jobs.length === 0) {
return <Loading>Loading...</Loading>
} else {
return (
<Router>
<React.Fragment>
<Route
exact
path="/"
render={() => (
<Home
jobs={filteredJobs}
searchChange={this.onSearchChange}
clickChange={this.onClickChange}
/>
)}
/>
onClickChange is what should update the state of tags
In my Home component I then simply pass the value on to the Categories component:
<Categories clickChange={clickChange} />
Finally it arrives in my Categories component where I say:
export default class Categories extends Component {
render() {
const { clickChange } = this.props
return (
<Wrapper>
<button value="Marketing" onClick={clickChange}>
<img
alt="Button"
src={require('/Users/markus/Documents/q4-2018/jobs-app/src/img/computer.png')}
/>
Frontend
</button> ...
Any ideas? Thx!
maybe you have to bind the "this" of "onClickChange", for example in the constructor of your App class.
Example :
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
jobs: jobs,
searchfield: '',
jobtags: ''
};
this.onClickChange = this.onClickChange.bind(this);
and it will work I think
You will have to bind it. Add this line to your constructor:
this.onClickChange = this.onClickChange.bind(this);