Using an object reference to set initial state in react - javascript

While trying to follow the DRY principle I began wondering if the following be encouraged/discouraged in react projects:
const DEFAULT_STATE = {
foo: foobar,
bar: barfoo
}
class Tuna extends Component {
constructor(props) {
super(props)
this.state = DEFAULT_STATE
}
...
reset = () => {
this.setState(DEFAULT_STATE)
}
...
}
or would this be any better (assuming that there are no objects nested inside DEFAULT_STATE)
const DEFAULT_STATE = {
foo: foobar,
bar: barfoo
}
class Tuna extends Component {
constructor(props) {
super(props)
this.state = {...DEFAULT_STATE}
}
...
reset = () => {
this.setState({...DEFAULT_STATE})
}
...
}
or just plain old this (repeating code)
class Tuna extends Component {
constructor(props) {
super(props)
this.state = {
foo: foobar,
bar: barfoo
}
}
...
reset = () => {
this.setState({
foo: foobar,
bar: barfoo
})
}
...
}
thank you.

In any case, you will have no problems if you do not change the initial object. But if you change object may experience the following problems, for example:
const DEFAULT_STATE = {
foo: foobar,
bar: barfoo
}
class Tuna extends Component {
constructor(props) {
super(props)
this.state = DEFAULT_STATE
}
...
reset = () => {
this.setState(DEFAULT_STATE)
}
someFunc = () => {
this.state.foo.push({field: 'lol'})
}
someFuncAfterReset = () => {
console.log(this.state.foo) // always [{field: 'lol'}]
}
}
Except the third case

Related

React not rendering an element when used in map() with {} [duplicate]

This question already has answers here:
When should I use a return statement in ES6 arrow functions
(6 answers)
Closed 4 years ago.
React doesn't render the element when I use this form of code:
class ListaPratitelja extends React.Component {
constructor(props) {
super(props);
}
render() {
const items = this.props.pratitelji;
const listItems = items.map((name, index) => {
e(Pratitelj, { key: index, name: name });
});
return listItems;
}
}
I've used a debugger to see what's going on, and the e function (which is just React.createElement) returns undefined.
But it works just fine when I use it like this:
class ListaPratitelja extends React.Component {
constructor(props) {
super(props);
}
render() {
const items = this.props.pratitelji;
const listItems = items.map((name, index) => e(Pratitelj, { key: index, name: name }));
return listItems;
}
}
The question is why? Is this a bug or I did something wrong in the first example. Also this is the full code:
'use strict';
const e = React.createElement;
class ListaPratitelja extends React.Component {
constructor(props) {
super(props);
}
render() {
const items = this.props.pratitelji;
const listItems = items.map((name, index) => e(Pratitelj, { key: index, name: name }));
return listItems;
}
}
class Pratitelj extends React.Component {
constructor(props) {
super(props);
this.state = { deleted: false };
this.handleDeleteChange = this.handleDeleteChange.bind(this);
}
handleDeleteChange(deletedState) {
this.setState({ deleted: deletedState });
}
render() {
console.log("rendered");
if (this.state.deleted) {
return '';
}
return e(
'div',
null,
e(PratiteljIme, { name: this.props.name }),
e(PratiteljDeleteButton, { handleDeleteChange: this.handleDeleteChange})
);
}
}
class PratiteljIme extends React.Component {
constructor(props) {
super(props);
}
render() {
return e(
"span",
null,
this.props.name)
}
}
class PratiteljDeleteButton extends React.Component {
constructor(props) {
super(props);
}
render() {
return e(
"button",
{ type: "button", "onClick": this.props.handleDeleteChange},
"X"
)
}
}
function loadListaPratitelja(pratitelji) {
const lista = pratitelji.split(",");
const domContainer = document.querySelector('#listaPratitelja');
ReactDOM.render(e(ListaPratitelja, {pratitelji: lista}), domContainer);
}
The input variable "pratitelji" is just a string with a couple of CSV (for ex. p1,p2,p3,p4).
The versions of react I use are these:
https://unpkg.com/react#16/umd/react.development.js
https://unpkg.com/react-dom#16/umd/react-dom.development.js
The browser I tested it on is the latest version of firefox for development.
with using { and } you are creating a function that doesn't return anything, unless you return something. Hence .map will return an array filled with undefined.
Update your code to this:
const listItems = items.map((name, index) => {
return e(Pratitelj, { key: index, name: name });
});
If you open up a function after the arrow, you must state what you're returning.

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.

Convert Back to ES6 Classes in JavaScript

I'm struggling what the equivalent syntax is for changing the following code to the equivalent ES6 syntax.
export default class EventSessionItem extends Component {
state = {
isDetailsVisible: false,
};
revealDetails = () => {
this.setState({isDetailsVisible: true});
};
I would have thought that the below would be the conversion but clearly not. It all lights up red as bad syntax.
export default class EventSessionItem extends Component {
constructor() {
isDetailsVisible = false;
}
function revealDetails {
this.setState({isDetailsVisible: true});
};
Your code with the class initialisers desugars to property assignments in the constructor:
export default class EventSessionItem extends Component {
constructor(...args) {
//^^^^^^^^^^^
super(...args);
this.state = {
// ^^^^^
isDetailsVisible: false,
};
this.revealDetails = () => {
// ^^^^^
this.setState({isDetailsVisible: true});
};
}
}
I think this is what you want. Assuming you are using React.
export default class EventSessionItem extends Component {
constructor(props) {
super(props);
this.state = {
isDetailsVisible: false
}
this.revealDetails = this.revealDetails.bind(this);
}
revealDetails() {
this.setState({isDetailsVisible: true});
}
}
The two class fields that you are using in the first example state = and revealDetails = () => are not part of the standard yet. https://github.com/tc39/proposal-class-fields
Follow this approach with some fixes from yours:
class Component {}
class EventSessionItem extends Component {
constructor() {
super();
this.isDetailsVisible = false;
}
setState(newState) {
this.isDetailsVisible = newState;
}
// This is to illustrate!
getState() {
return this.isDetailsVisible;
}
revealDetails() {
this.setState({
isDetailsVisible: true
});
};
}
var esi = new EventSessionItem();
esi.revealDetails();
console.log(esi.getState());
.as-console-wrapper { max-height: 100% !important; top: 0; }

Reset initial state in React + ES6

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

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