How to replace componentWillReceiveProps with getDerivedStateFromProps - javascript

I read and tried so hard, but still could not find a way to successfully replace componentWillReceiveProps with getDerivedStateFromProps.
Here is my code, componentWillReceiveProps one is working fine.
but the getDerivedStateFromProps which I tried is not working(after componentDidMount() running OK, state.movies eventually become [] empty).
Thanks for your kind help!
class OK_MovieListContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: [],
title:''
};
this.discoverMovieDB = this.discoverMovieDB.bind(this);
this.searchMovieDBAPI = this.searchMovieDBAPI.bind(this);
}
componentDidMount() {
this.discoverMovieDB();
}
UNSAFE_componentWillReceiveProps(nextProps, prevState) {
if (nextProps.match.params.title && prevState.title !== nextProps.match.params.title) {
this.searchMovieDBAPI(nextProps.match.params.title);
this.setState({ title: nextProps.match.params.title });
}
}
async discoverMovieDB() {
.....
}
async searchMovieDBAPI(title) {
const promisedData = await MovieDBAPI.search(title);
if (promisedData) {
this.setState({ movies: promisedData[0] });
}
}
}
render() {
return (
<div className="App">
<MovieList movies={this.state.movies} />
</div>
);
}
}
export default OK_MovieListContainer;
the following is what I tried with getDerivedStateFromProps , but not working(after componentDidMount() running OK, state.movies eventually become [] empty). ....T.T
class MovieListContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: [],
title:''
};
this.discoverMovieDB = this.discoverMovieDB.bind(this);
this.searchMovieDBAPI = this.searchMovieDBAPI.bind(this);
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.match.params.title && nextProps.match.params.title !== prevState.title) {
return {
//this state changes then componentDidUpdate is called
title: nextProps.match.params.title
};
}
//indicate state update not required
return null;
}
componentDidMount() {
this.discoverMovieDB();
}
componentDidUpdate(nextProps, prevState) {
if (nextProps.match.params.title !== prevState.title) {
this.searchMovieDBAPI(nextProps.match.params.title);
this.setState({ title: nextProps.match.params.title });
}
}
async discoverMovieDB() {
.....
}
async searchMovieDBAPI(title) {
const promisedData = await MovieDBAPI.search(title);
if (promisedData) {
this.setState({ movies: promisedData[0] });
}
}
}
render() {
return (
<div className="App">
<MovieList movies={this.state.movies} />
</div>
);
}
}
export default MovieListContainer;
Thank you so much!

Related

HandleClick not changing the state

I have the below where it should display images of beers retrieved from an API. Each image has a handleClick event which will direct them to a details page about this beer. My code below doesn't render the beers at all and goes straight to the details page of a random beer. Can anyone help me figure out why?
Thanks
export default class GetBeers extends React.Component {
constructor() {
super();
this.state = {
beers: [],
showMethod: false,
beerDetails: []
};
this.getBeerInfo = this.getBeerInfo.bind(this);
this.handleClick = this.handleClick.bind(this);
}
handleClick(details) {
this.setState({
showMethod: !this.state.showMethod,
beerDetails: details
});
}
render() {
if(this.state.showMethod) {
return (
<Beer details = {this.state.beerDetails}/>
);
}
else {
return (
<div>{this.state.beers.map(each=> {
return <img className = "img-beer" onClick = {this.handleClick(each)} src={each.image_url}/>
})}</div>
);
}
}
componentDidMount() {
this.getBeerInfo()
}
getBeerInfo() {
...gets info
}
}
When you use onClick like that you run the function at the render.
So you have to use arrow function:
Not Working:
<img className = "img-beer" onClick = {this.handleClick(each)} src={each.image_url}/>
Working:
<img className = "img-beer" onClick = {() => this.handleClick(each)} src={each.image_url}/>
The main issue is not calling the handle properly.
Also, I noticed that you are binding the functions in the constructor. It might be simpler to use ES6 function creation, so the scope of the class is bound to your handle method.
export default class GetBeers extends React.Component {
constructor() {
super();
this.state = {
beers: [],
showMethod: false,
beerDetails: []
};
}
handleClick = (details) => {
this.setState({
showMethod: !this.state.showMethod,
beerDetails: details
});
}
render() {
if(this.state.showMethod) {
return (
<Beer details = {this.state.beerDetails}/>
);
}
else {
return (
<div>{this.state.beers.map(each=> {
return <img className = "img-beer" onClick = {() => this.handleClick(each)} src={each.image_url}/>
})}</div>
);
}
}
componentDidMount() {
this.getBeerInfo()
}
getBeerInfo = () => {
...gets info
}
}

How to call child's method from parent without using Refs?

Let's say I've a parent component A and a child B:
A:
class A {
constructor() {
this.state = {data: []};
}
handleClick = () => {
// api call
// set data state to the returned value from api
// call B's createTable method
}
render() {
return(
<div>
<button onClick={()=> this.handleClick()}>Fetch data</button>
<B data={this.state.data} />
</div>
}
}
B:
class B {
constructor() {
this.state = {...};
}
createTable = () => {
const { data } = this.props;
// do smth
}
render() {
return(...);
}
}
I want to call createTable method from A without using Refs.
What I've done so far is using componentDidUpdate life cycle method in B to check if data prop has changed or not, If it changed call createTable method but I want to know is this right? or there's a better way of doing it because I feel it is kinda hacky or maybe bad design.
class B {
constructor() {
this.state = {...};
}
componentDidUpdate(prevProps) {
const { data } = this.props;
if (data !== prevProps.data) {
this.createTable();
}
}
createTable = () => {
const { data } = this.props;
// do smth
}
render() {
return(...);
}
}
NOTE I don't want to use hooks either just class based component.
The following example might be useful
class Parent extends Component {
render() {
return (
<div>
<Child setClick={click => this.clickChild = click}/>
<button onClick={() => this.clickChild()}>Click</button>
</div>
);
}
}
class Child extends Component {
constructor(props) {
super(props);
this.getAlert = this.getAlert.bind(this);
}
componentDidMount() {
this.props.setClick(this.getAlert);
}
getAlert() {
alert('clicked');
}
render() {
return (
<h1 ref="hello">Hello</h1>
);
}
}

React: calling a function in the onClick returns undefined

I'm trying to write a code that shows a list of images, etc. based on this answer:
https://stackoverflow.com/a/57635373/9478434
My code is:
class ImageGallery extends Component {
constructor(props) {
super(props);
this.state = {
photoIndex: 0,
isOpen: false,
imageList: [],
};
}
getImages() {
axios
.get(IMAGE_LIST_URL, {})
.then((response) => {
const data = response.data;
console.log(data);
this.setState({ imageList: response.data });
})
.catch((error) => {
setTimeout(() => {
console.log(error.response.data.message);
}, 200);
});
}
componentDidMount() {
this.getImages();
}
changePhotoIndex(imgIndex) {
this.setState({ photoIndex: imgIndex, isOpen: true });
}
render() {
const { photoIndex, isOpen, imageList } = this.state;
const singleImage = imageList.map(function (img, imgIndex) {
const imagePath = `http://localhost:8000/media/${img.filePath}`;
console.log(imagePath);
return (
<figure className="col-xl-3 col-sm-6">
<img
src={imagePath}
alt="Gallery"
className="img-thumbnail"
onClick={() => this.changePhotoIndex(imgIndex)}
/>
</figure>
);
});
return <div>{singleImage}</div>;
}
}
However while clicking on the image, I get a type error (t is undefined) in the console regarding to the line onClick={() => this.changePhotoIndex(imgIndex) and the state of app does not change.
The changePhoneIndex function considers itself and not the component as this.
You can bind it to the component itself and be able to access setState by adding this to the constructor:
constructor(props) {
super(props);
this.state = {
photoIndex: 0,
isOpen: false,
imageList: [],
};
this.changePhotoIndex.bind(this);
}
Or you can call set state directly:
onClick={() => this.setState({ photoIndex: imgIndex, isOpen: true })}
You forgot to bind your function. Add the following to your code:
constructor(props) {
super(props);
this.state = {
photoIndex: 0,
isOpen: false,
imageList: [],
};
this.changePhotoIndex = this.changePhotoIndex.bind(this); // missing
}

Warning react : setState(...): Can only update a mounted or mounting component

Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op.
This is a react application, where a banner is fixed on the screen and passing random images. The way it was written is generating the warning in question.
import React from "react";
import Lightbox from "react-image-lightbox";
import logo from "./logo.png";
class Banner extends React.Component {
constructor(props) {
super(props);
this.state = {
images: [],
currentImage: logo,
isOpen: false,
sidebarOpen: true
};
}
async componentWillMount() {
await this.getBanners();
this.setState({ currentImage: this.state.images[0].url });
setInterval(async () => {
await this.getBanners();
}, 300000);
let i = 0;
setInterval(
() => {
this.setState({ currentImage: this.state.images[i].url });
if (i >= this.state.images.length - 1) {
i = 0;
} else {
i++;
}
},
10000,
i
);
}
async getBanners() {
const data = await (await fetch("/api/banners/active")).json();
if (data.true) {
this.setState({ images: data.true });
}
}
render() {
const { isOpen } = this.state;
return (
<div>
{isOpen && (
<Lightbox
mainSrc={this.state.currentImage}
onCloseRequest={() => this.setState({ isOpen: false })}
/>
)}
<footer>
<a>
<img
width={270}
height="200"
src={this.state.currentImage}
onClick={() => this.setState({ isOpen: true })}
alt="idk"
/>
</a>
</footer>
</div>
);
}
}
export default Banner;
Could anyone help improve this code?
You can put the numbers returned from setInterval on your instance and stop the intervals with clearInterval in componentWillUnmount so that they won't continue to run after the component has been unmounted.
class Banner extends React.Component {
constructor(props) {
super(props);
this.bannerInterval = null;
this.currentImageInterval = null;
this.state = {
images: [],
currentImage: logo,
isOpen: false,
sidebarOpen: true
};
}
async componentDidMount() {
await this.getBanners();
this.setState({ currentImage: this.state.images[0].url });
this.bannerInterval = setInterval(async () => {
await this.getBanners();
}, 300000);
let i = 0;
this.currentImageInterval = setInterval(
() => {
this.setState({ currentImage: this.state.images[i].url });
if (i >= this.state.images.length - 1) {
i = 0;
} else {
i++;
}
},
10000,
i
);
}
componentWillUnmount() {
clearInterval(this.bannerInterval);
clearInterval(this.currentImageInterval);
}
// ...
}
Use this template for any class-based component that has a state:
forgot about setState(), and use setComponentState declared down:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
// other fields...
isUnmounted: false,
};
}
componentWillUnmount() {
this.setState({ isUnmounted: true });
}
setComponentState = (values) => {
if (!this.state.isUnmounted) this.setState(values);
};
}

How can I update High Order Component

I created array of routing in ReactJS
const routes = [
{ id: 0, path: '/', view: Home, parent: 0 },
{ id: 1, path: '/a', view: Home2, parent: 0 },
{ id: 2, path: '/b', view: Home3, parent: 1 }
]
Created HOC withAuth which should back to parent routing when user isn't logged. When i going to route (as not logged) - its ok and withAuth back me to parent route, but when i am on route and logout page isn't refresh and I am stay on route for logged users.
import React, { Component } from "react";
import AuthHelper from "./AuthHelper";
export default function withAuth(AuthComponent) {
const Auth = new AuthHelper();
class AuthWrapped extends Component {
constructor(props) {
super(props);
this.state = {
confirm: null,
loaded: false
};
}
checkLogged = () => {
if (!Auth.loggedIn()) {
const parent = this.props.parent;
const obj = this.props.routes
.filter(v => v.id === parent);
this.props.history.replace(obj[0].path);
} else {
try {
const confirm = Auth.getConfirm();
this.setState({
confirm: confirm,
loaded: true
});
} catch (err) {
Auth.logout();
this.props.history.replace("/");
}
}
}
componentDidMount() {
this.checkLogged();
}
render() {
if (this.state.loaded) {
if (this.state.confirm) {
return (
<AuthComponent
history={this.props.history}
confirm={this.state.confirm}
/>
);
} else {
return null;
}
} else {
return null;
}
}
};
return AuthWrapped;
}
I believe that you are using the authentication system the wrong way
In React everything should exist in a hierarchical manner.
In your case, you have an Auth state that would change and when the loggedIn state changes, everything should re-render. the correct way to do this is using the Context API to handle the logged in state so when the state changes, the whole screen would re-render
here is the solution to your problem:
AuthContext.js
const AuthContext = React.createContext();
export class AuthProvider extends React.Component {
state = {
isLoggedIn: false,
};
login = (username, password) => {
someLoginRequestToServer(username, password).then(response => {
this.setState({
isLoggedIn: response.isLoggedIn,
});
});
};
logout = () => {
someLogoutRequestToServer().then(() => {
this.setState({ isLoggedIn: false });
});
};
render() {
return (
<AuthContext.Provider
value={{
loggedIn: this.state.isLoggedIn,
login: this.login,
logout: this.logout,
}}>
{this.props.children}
</AuthContext.Provider>
);
}
}
export const AuthConsumer = AuthContext.Consumer;
SomeCustomAuthComponent
class CustomAuthComponent extends React.Component {
render() {
return (
<AuthConsumer>
{({ loggedIn, login, logout }) => (
<div>
<p>You Are {loggedIn ? 'Logged in' : 'Logged out'}</p>
<button onClick={loggedIn ? () => logout() : () => login('abcd', '12345')} />
</div>
)}
</AuthConsumer>
);
}
}
Or you can use the redux for state management and react-redux as it uses the react Context API under the hood.
hope this helps you! :)
the problem lays here
componentDidMount() {
this.checkLogged();
}
you're checking if the user is logged only when the component is mounted (after the instantiation). you should be checking it every time the page updates, you have to identify a way to handle this mechanism for example by using the componentDidUpdate hook.
export default function withAuth(AuthComponent) {
const Auth = new AuthHelper();
class AuthWrapped extends Component {
constructor(props) {
super(props);
this.state = {
confirm: null,
loaded: false
};
}
checkIsNotLogged = () => {
const parent = this.props.parent;
const obj = this.props.routes
.filter(v => v.id === parent);
this.props.history.replace(obj[0].path);
}
checkLogged = () => {
if (!Auth.loggedIn()) {
this.checkIsNotLogged();
} else {
try {
const confirm = Auth.getConfirm();
this.setState({
confirm: confirm,
loaded: true
});
} catch (err) {
Auth.logout();
this.props.history.replace("/");
}
}
}
componentDidMount() {
this.checkLogged();
}
componentDidUpdate() {
// do not call here the checkLogged method otherwise you could trigger an infinite loop
this.checkIsNotLogged();
}
render() {
if (this.state.loaded) {
if (this.state.confirm) {
return (
<AuthComponent
history={this.props.history}
confirm={this.state.confirm}
/>
);
} else {
return null;
}
} else {
return null;
}
}
};
return AuthWrapped;
}

Categories

Resources