Why react doesn't call render when state is changed? - javascript

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");
//...
}}

Related

Dynamically update value based on State in React

I have a react class based component where I have defined a state as follows:
class MyReactClass extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedDataPoints: new Set()
};
}
// This method is called dynamically when there is new addition of data
storeData = (metricName, dataPoint) => {
if (this.state.selectedDataPoints.has(dataPoint)) {
this.state.selectedDataPoints.delete(dataPoint);
} else {
this.state.selectedDataPoints.add(dataPoint);
}
};
render () {
return (
<p>{this.state.selectedDataPoints}</p>
);
}
}
Note that initially, the state is an empty set, nothing is displayed.
But when the state gets populated eventually, I am facing trouble in spinning up the variable again. It is always taking as the original state which is an empty set.
If you want the component to re-render, you have to call this.setState () - function.
You can use componentshouldUpdate method to let your state reflect and should set the state using this.state({}) method.
Use this code to set state for a set:
export default class Checklist extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedDataPoints: new Set()
}
this.addItem = this.addItem.bind(this);
this.removeItem = this.removeItem.bind(this);
}
addItem(item) {
this.setState(({ selectedDataPoints }) => ({
selectedDataPoints: new Set(selectedDataPoints).add(item)
}));
}
removeItem(item) {
this.setState(({ selectedDataPoints }) => {
const newSelectedDataPoints = new Set(selectedDataPoints);
newSelectedDataPoints.delete(item);
return {
selectedDataPoints: newSelectedDataPoints
};
});
}
getItemCheckedStatus(item) {
return this.state.checkedItems.has(item);
}
// This method is called dynamically when there is new addition of data
storeData = (metricName, dataPoint) => {
if (this.state.selectedDataPoints.has(dataPoint)) {
this.state.selectedDataPoints.removeItem(dataPoint);
} else {
this.state.selectedDataPoints.addItem(dataPoint);
}
};
render () {
return (
<p>{this.state.selectedDataPoints}</p>
);
}
}

useState in class component

can anyone tell me how to transfer it to form acceptable in class component? Thanks
const [isAuth, setIsAuth] = useState(localStorage.getItem("isAuth"));
Something like the following
define the state in the constructor and initialize it with the value you use in the useState
create a setIsAuth method that sets the state to the new value passed
class YourComponent extends React.Component{
constructor(props){
super(props);
this.state = {
isAuth: localStorage.getItem("isAuth")
}
}
setIsAuth = (newValue) => {
this.setState({
isAuth: newValue
});
}
render() {
...
}
}

React/Redux: Where to put jQuery event handlers in component awaiting async data

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);

How to inject functions that change component state?

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.

Questions about React and creating timers when a component is created

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 ...
}
};

Categories