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.
Related
At the moment, I have the following routes in my App.js file:
<Switch>
<Route exact path="/new-job"
render={(props) => <NewJob jobName={jobName} setMenuSelection={handleMenuSelection} />}
/>
<Route exact path="/past-jobs"
render={(props) => <PastJobs setMenuSelection={handleMenuSelection} />}
/>
</Switch>
Now within my PastJobs component, I have the following button with onClick process:
<Button
onClick={() => {
setConfirmDialog({
isOpen: true,
title: `Copy Job ${item.id}?`,
onConfirm: () => { onCopy(item.job_info) }
})
}}
>
Copy
</Button>
that calls the following function:
const onCopy = (job_info) => {
setConfirmDialog({
...confirmDialog,
isOpen: false
})
history.push({
pathname: '/new-job',
state: { detail: job_info }
})
}
Within my <NewJob /> component, I have now setup the following as I thought I could access the state.detail but unfortunately it's null, i.e.:
import { useLocation } from 'react-router-dom';
function NewJob( { jobName, setMenuSelection } ) {
const { state } = useLocation();
if (typeof state !== 'undefined') {
const myVal = state.detail
console.log("myVal", myVal )
}
}
The issue that I am having and unsure how to approach is that within my onCopy function that is called from button onClick, how do I call the the <NewJob /> component whose path is exact path="/new-job" in App.js above and pass in the prop job_info ?
Direct calls to components actually does not exist. But what you are looking can be achieved in different ways.
Using state machine with event bus (redux, redux-saga)
Render props https://reactjs.org/docs/render-props.html
Bunch of callbacks drilled via props (HOC's)
Ref's https://reactjs.org/docs/refs-and-the-dom.html
I suggest to read more about them to actually understand if it matches your use case. Anyhow it is great experience to develop your skills also!
This is my render method in App.js:
render() {
console.log('APP Mounted', this.state.searchedBooks)
return (
<div className="app">
<Route exact path='/' render={()=> (
<ListBooks
books={this.state.books}
handleChangeCategory={(choice) => this.handleChangeCategory(choice)}
/>
)}/>
<Route path='/search' render={({ history }) => (
<SearchBook
handleChangeCategory={(choice) => this.handleChangeCategory(choice)}
handleSearchBook={(query) => this.handleSearchBook(query)}
searchedBooks={this.state.searchedBooks}
/>
)}/>
</div>
)}
I have searchedBook state that keeps updating when a new query arrives, and therefore also rerenders App.js. The log at the top of the render() method works as expected.
The problem is, that my child component SearchBook is not rerendering when App.js is rerendered and therefore doesn't receive the latest this.state.searchedBooks
In SearchBook.js I added the componentDidMount() method to reset the state:
class SearchBook extends Component {
state = {
query : '',
books : []
}
componentDidMount(){
console.log('Mounted')
this.setState(() => ({
books: this.props.searchedBooks
}))
}
updateQuery = (query) => {
this.setState(() => ({
query: query
}))
}
onChangeQuery = query => {
this.updateQuery(query)
this.props.handleSearchBook(query)
}
render(){
const { query, books } = this.state;
return()
}}
But this doesn't work as the whole SearchBook component doesn't get rerendered after my searchedBooks state is updated in App.js.
When I manually access a different URL (here the home route) and go back to the /search route, obviously the component gets rerendered and it works.
So how do I make the child of App.js rerender as well?
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
});
Here is the parent component :
state = {
books: undefined
}
componentDidMount() {
BooksAPI.getAll().then(books => {
this.setState({books},this.filterBooks);
});
}
filterBooks() {
this.currentlyReading = this.state.books.filter((book) => book.shelf === 'currentlyReading');
this.read = this.state.filter((book) => book.shelf === 'read');
this.wantToRead = this.state.filter((book) => book.shelf === 'wantToRead');
}
render() {
return (
<div className="app">
<Route path="/search" component={SearchBook} />
<Route exact
path="/"
render={() => <BookScreen
currentlyReading={this.currentlyReading}
read={this.read}
wantToRead={this.wantToRead}
/>} />
</div>
)
}
I expect that the props will change for BookScreen component after filterBooks is called and the component should rerender, but it doesn't. What am I doing wrong?
In my opinion, the best way for now, which still follows React's way is: after having books data, call 1 function only, and this function will process everything and update state after finishing (we pass the books object as parameter), like this:
componentDidMount() {
BooksAPI.getAll().then(books => {
this.filterBooks({books});
});
}
filterBooks = (books) => {
this.currentlyReading = books.filter((book) => book.shelf === 'currentlyReading');
this.read = books.filter((book) => book.shelf === 'read');
this.wantToRead = books.filter((book) => book.shelf === 'wantToRead');
this.setState({ books: books });
}
If you have any error, feel free to post here then we can get through together!
========
Added explanation on why the author's original code doesn't work:
Based on my little experience with React and JS:
When a new state is set, it may take time (maybe 100-300ms, that is why you execute the original this.filterBooks using the syntax this.setState({books},this.filterBooks)
=> This seems to be right, which means after a new state of books is already set, you can access it the filterBooks function.)
HOWEVER: after new state of books is set, the page will be re-rendered, and filterBooks will be executed (perhaps at the same time => not sure which one ends first, so let's say for example, this.currentlyReading is still undefined in render() if render() happens first, before the result of filterBooks is completely set!
In other words, React is Javascript, and Javascript's asynchronization is troublesome!
You can try to do this, Just update the books state like this:
componentDidMount() {
BooksAPI.getAll().then(books => this.setState({books}));
}
This will cause a re-render. But since you're not using the state directly to populate your child component, we need to call the filterBooks() inside the render() method.
render() {
this.filterBooks()
return (
<div className="app">
<Route path="/search" component={SearchBook} />
<Route exact
path="/"
render={() => <BookScreen
currentlyReading={this.currentlyReading}
read={this.read}
wantToRead={this.wantToRead}
/>} />
</div>
)
}
The call to that method will update the data that you pass as props to your child component.