I want to create a component that hides itself after a few seconds. I have 2 questions:
Where should I place the setTimeout? Probably in the constructor, componentWillMount, or componentDidMount. Is there a differences in placing it in componentWillMount or componentDidMount?
Is it bad practice to do this.state = ... outside the constructor?
Here's what I have:
class Message extends React.Component {
constructor() {
super();
this.state = {
closed: false,
closeTimeout: null
};
}
componentDidMount() {
if (this.props.closeAfter) {
this.state.closeTimeout = setTimeout(_ => {
this.setState({closed: true});
}, this.props.closeAfter);
}
}
componentWillUnmount() {
if (this.state.closeTimeout) {
clearTimeout(this.state.closeTimeout);
}
}
render() {
return ...
}
};
Related
When I read the ReactJS docs I understand that the best lifecycle method to put redux action calls is componentDidMount but I find it hard to do any jquery work in that lifecycle method:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isAsyncLoaded: false
}
}
static getDerivedStateFromProps(nextProps, prevState) {
let {getData} = nextProps.someData;
let {hasSuccess, isLoading} = getData;
if (!isLoading && hasSuccess) {
return {
isAsyncLoaded: true
}
} else {
return {
isAsyncLoaded: false
}
}
return null;
}
componentDidMount() {
this.props.getSomeData();
$('p').css('color', 'red');
}
render() {
if (!this.state.isAsyncLoaded) return null;
return (
<p>{this.props.someData.data}</p>
)
}
}
function mapStateToProps(state) {
return {
someData: state.someData
}
}
export default connect(mapStateToProps, {getSomeData})(App);
Here the jquery $('p').css('color', 'red'); part doesn't find the <p> element because isAsyncLoaded is not true and render function returns null.
My question is how can I solve this problem without introducing a container-view, (parent-child) component relationship to my app?
Apply jquery code in componentDidUpdate()
componentDidUpdate(prevProps, prevState) {
// execute code only when at the moment when async code is first rendered
if (prevState.isAsyncLoaded === false && this.state.isAsyncLoaded === true) {
// Do your jQuery stuff here
$(this.mountPoint).find(...);
}
}
live demo: https://stackblitz.com/edit/react-ee57nm
Just a few notes before I get into this: Using event handlers like jQuery is usually an anti-pattern in React. It can cause unintended side effects on your DOM, that should ideally be handled entirely through controlled components within react.
If you need to use jQuery though (For a quick/dirty plugin, or things like that), I recommend using a separate component. When that component gets mounted, it fires off a componentDidMount event, which you can hook and fire off your jQuery code:
class MyJqueryComponent extends React.Component {
constructor(props) {
super(props);
this.mountPoint = React.createRef();
}
componentDidMount() {
// Do your jQuery stuff here
$(this.mountPoint).find(...);
}
render() {
return (
<p ref={this.mountPoint}>{this.props.data}</p>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isAsyncLoaded: false
}
}
static getDerivedStateFromProps(nextProps, prevState) {
let {getData} = nextProps.someData;
let {hasSuccess, isLoading} = getData;
if (!isLoading && hasSuccess) {
return {
isAsyncLoaded: true
}
} else {
return {
isAsyncLoaded: false
}
}
return null;
}
componentDidMount() {
this.props.getSomeData();
$('p').css('color', 'red');
}
render() {
if (!this.state.isAsyncLoaded) return null;
return (
<p><MyJqueryComponent data={this.props.someData.data} /></p>
)
}
}
function mapStateToProps(state) {
return {
someData: state.someData
}
}
export default connect(mapStateToProps, {getSomeData})(App);
I want to keep some functions outside of my component for easier testing. However, I cannot change state with these functions because they cannot reference the component's state directly.
So I currently have the hacky solution where I set the function to a variable then call this.setState. Is there a better convention/more efficient way to do this?
Example function code in Tester.js:
const tester = () => {
return 'new data';
}
export default tester;
Example component code in App.js (without imports):
class App extends Component {
constructor() {
super();
this.state = {
data: ''
}
}
componentDidMount(){
let newData = tester();
this.setState({ data: newData })
}
render() {
return(
<div>{this.state.data}</div>
)
}
}
You could bind your tester function like this (this approach doesn't work with arrow functions):
function tester() {
this.setState({ data: 'new Data' });
}
class App extends Component {
constructor() {
super();
this.state = {
data: '',
};
this.tester = tester.bind(this);
}
componentDidMount() {
this.tester();
}
render() {
return (
<div>{this.state.data}</div>
);
}
}
But I would prefer a cleaner approach, where you don't need your function to access this (also works with arrow functions):
function tester(prevState, props) {
return {
...prevState,
data: 'new Data',
};
}
class App extends Component {
constructor() {
super();
this.state = {
data: '',
};
}
componentDidMount() {
this.setState(tester);
}
render() {
return (
<div>{this.state.data}</div>
);
}
}
You can pass a function to setState() that will return a new object representing the new state of your component. So you could do this:
const tester = (previousState, props) => {
return {
...previousState,
data: 'new data',
};
}
class App extends Component {
constructor() {
super();
this.state = {
data: ''
}
}
componentDidMount(){
this.setState(tester)
}
render() {
return(
<div>{this.state.data}</div>
)
}
}
The reason being that you now have access to your component's previous state and props in your tester function.
If you just need access to unchanging static placeholder values inside of your app, for example Lorem Ipsum or something else, then just export your data as a JSON object and use it like that:
// testData.js
export const testData = {
foo: "bar",
baz: 7,
};
...
// In your app.jsx file
import testData from "./testData.js";
const qux = testData.foo; // "bar"
etc.
I'm trying to create variables and a function inside a state like this
state = {
modalVisible: false,
photo:""
getDataSourceState
}
which i have done, how can i call the function outside the state and set a new state.
This what i have done but i keep getting errors
getDataSourceState() {
return {
dataSource: this.ds.cloneWithRows(this.images),
};
}
this.setState(this.getDataSourceState());
see what prompted me to ask the question, because i was finding it difficult to access modalVisible in the state since there is a this.state = this.getDataSource()
constructor(props) {
super(props);
this.state = {
modalVisible: false,
photo:"",
sourceState: getDataSourceState()
}
this.ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.lastPhotoFetched = undefined;
this.images = [];
this.fetchPhotos();
this.getDataSourceState = this.getDataSourceState.bind(this)
}
componentDidMount(){
this.getDataSourceState();
}
getDataSourceState() {
return {
dataSource: this.ds.cloneWithRows(this.images),
};
}
getPhotosFromCameraRollData(data) {
return data.edges.map((asset) => {
return asset.node.image;
});
}
}
You can't the way you have attempted but technically yes, you can have a function that returns the desired state you want initialised in your constructor. I wouldn't suggest doing it though.
You will quickly run into issues where your components aren't updating state correctly.
What you are looking for is a function that returns a value as opposed to sets state. You would do something like this:
constructor(){
super()
this.state = {
modalVisible: false,
photo:""
sourceState: this.getDataSourceState()
}
this.getDataSourceState = this.getDataSourceState.bind(this)
}
getDataSourceState(){
return this.ds.cloneWithRows(this.images)
}
As I mentioned, it is not a good idea to do it this way. You are better off initialising the state values as a default value and then setting the state in your componentDidMount like so:
constructor(){
super()
this.state = {
modalVisible: false,
photo:""
sourceState: null
}
this.getDataSourceState = this.getDataSourceState.bind(this)
}
componentDidMount(){
this.getDataSourceState()
}
getDataSourceState(){
const data = this.ds.cloneWithRows(this.images)
this.setState({soureState: data})
}
This way you have a reusable function which you can call in componentDidUpdate() if need be for when you move navigate between the same component with different data and want the state to update.
Yes you can.
class App extends Component {
func1 = () => {
this.setState({flag:!this.state.flag})
}
state = {
flag:true,
doclick:this.func1
}
}
I have a class, ElementBuilder below, and when the user saves the Element they've built, I want the state to reset to the values below.
I have some functions in this class that I haven't provided but that change the state of title, size, and color.
In ES 5, I would have a getInitialState function on my class and could call this.getInitialState() in a function.
This element lives in my app for the lifecycle of a logged in user and I want the default values to always be the same regardless of past usage.
How do I achieve this without writing a function that sets an object of default values (or maybe that's the answer)? thanks!
class ElementBuilder extends Component {
constructor(props) {
super(props);
this.state = {
title: 'Testing,
size: 100,
color: '#4d96ce',
};
}
resetBuilder() {
this.setState({ this.getInitialState() });
}
}
You may use a getter function:
class ElementBuilder extends Component {
constructor(props) {
super(props);
this.state = this.initialState;
}
get initialState() {
return {
title: 'Testing',
size: 100,
color: '#4d96ce',
};
}
resetBuilder() {
this.setState(this.initialState);
}
}
or just a variable:
constructor(props) {
super(props);
this.initialState = {
title: 'Testing',
size: 100,
color: '#4d96ce',
};
this.state = this.initialState;
}
Using the proposed class fields, you could do something like this:
class ElementBuilder extends Component {
static initialState = {
title: 'Testing',
size: 100,
color: '#4d96ce'
}
constructor(props) {
super(props)
this.state = ElementBuilder.initialState
}
resetBuilder() {
this.setState(ElementBuilder.initialState)
}
}
Since the initial state doesn't seem to depend on anything instance specific, just define the value outside the class:
const initialState = {...};
class ElementBuilder extends Component {
constructor(props) {
super(props);
this.state = initialState;
}
resetBuilder() {
this.setState(initialState);
}
}
Use an High Order Component to clear component state (rerender)
Exemple Element.jsx :
// Target ----- //
class Element extends Component {
constructor(props){
super(props)
const {
initState = {}
} = props
this.state = {initState}
}
render() {
return (
<div className="element-x">
{...}
</div>
)
}
}
// Target Manager ----- //
class ElementMgr extends Component {
constructor(props){
super(props)
const {
hash = 0
} = props
this.state = {
hash, // hash is a post.id
load: false
}
}
render() {
const {load} = this.state
if (load) {
return (<div className="element-x"/>)
} else {
return (<Element {...this.props}/>)
}
}
componentWillReceiveProps(nextProps) {
const {hash = 0} = nextProps
if (hash !== this.state.hash) {
this.setState({load:true})
setTimeout(() => this.setState({
hash,
load:false
}),0)
}
}
}
export default ElementMgr
I have problem with automatically re-rendering view, when state is changed.
State has been changed, but render() is not called. But when I call this.forceUpdate(), everything is ok, but I think that's not the best solution.
Can someone help me with that ?
class TODOItems extends React.Component {
constructor() {
super();
this.loadItems();
}
loadItems() {
this.state = {
todos: Store.getItems()
};
}
componentDidMount(){
//this loads new items to this.state.todos, but render() is not called
Store.addChangeListener(() => { this.loadItems(); this.forceUpdate(); });
}
componentWillUnmount(){
Store.removeChangeListener(() => { this.loadItems(); });
}
render() {
console.log("data changed, re-render");
//...
}}
You should be using this.state = {}; (like in your loadItems() method) from the constructor when you are declaring the initial state. When you want to update the items, use this.setState({}). For example:
constructor() {
super();
this.state = {
todos: Store.getItems()
};
}
reloadItems() {
this.setState({
todos: Store.getItems()
});
}
and update your componentDidMount:
Store.addChangeListener(() => { this.reloadItems(); });
You sholdn't mutate this.state directly. You should use this.setState method.
Change loadItems:
loadItems() {
this.setState({
todos: Store.getItems()
});
}
More in react docs
In your component, whenever you directly manipulate state you need to use the following:
this.setState({});
Complete code:
class TODOItems extends React.Component {
constructor() {
super();
this.loadItems();
}
loadItems() {
let newState = Store.getItems();
this.setState = {
todos: newState
};
}
componentDidMount(){
//this loads new items to this.state.todos, but render() is not called
Store.addChangeListener(() => { this.loadItems(); this.forceUpdate(); });
}
componentWillUnmount(){
Store.removeChangeListener(() => { this.loadItems(); });
}
render() {
console.log("data changed, re-render");
//...
}}