Is this good practice store state in variable? - reactjs

I have just started to study ReactJS and have some questions. I was reading documentation here, but I can't find the answer I am looking for. Here is an example:
var Awesome = React.createClass({
getInitialState:function() {
return {
txt : ["1","2","3","4","5"],
isTrue : true
}
},
handleClick:function() {
this.setState({
isTrue : !this.state.isTrue
})
},
render:function() {
var changeStyle = {
display: this.state.isTrue ? "block" : "none"
};
var message = this.state.txt.map(function(oneMessage) {
return <SubChild change={changeStyle} txt={oneMessage}/>
});
return (
<div>
<button onClick={this.handleClick} >Click Me</button>
{message}
</div>
)
}
})
var SubChild = React.createClass({
render:function() {
return (
<div style={this.props.change}>
<h3>{this.props.txt}</h3>
</div>
)
}
})
React.render(<Awesome />, document.body)
Everything works fine, but I have some questions. As you can see I store my state inside a variable. Is this the best practice? How can I achieve the same result without variables inside render function or actually without states (I am trying to avoid state). Is this possible?
Here is my Fiddle

Why State Variables?
The idea of using state variables is to have changing / dynamic data, ie if anything about the component is changing, it should be defined as a state variable in the component so user interaction can result in change of this variable and a change in this variable causes the effected component to re-render.
Use of Properties
If some value is changed for each instance of the component and is uneffected by user interaction or component state change, it should be defined as a property so it can be assigned only once at instantiation.
In all cases, we cannot really avoid the use of variables inside the render

Related

Im trying to build a menu that has collapsible options, but the dom does not update even when state data updates

const [arrayOfQuestions, setArrayOfQuestions] = useState([
{
q: 'Are your products safe to use?',
a: 'Yes.',
hidden: true
},
{
q: 'Are your products safe to use?',
a: 'Yes.',
hidden: true
},
{
q: 'Are your products safe to use?',
a: 'Yes.',
hidden: true
},
{
q: 'Are your products safe to use?',
a: 'Yes.',
hidden: true
},
])
const toggleItemOpenAndClose = (e) => {
let array = arrayOfQuestions
array[e.target.id].hidden = !array[e.target.id].hidden
setArrayOfQuestions(array)
}
return (
<div>
<Layout
bgImage={metaData.heroImage.childImageSharp.fluid}
header='Frequently Asked Questions'>
<div className='page-container'>
<div className="content-container">
{
arrayOfQuestions.map((question,i) => {
return (
<div id={i} key={`id${i}`} onClick={toggleItemOpenAndClose} className='block-container'>
<div id={i} className='white smallerheader'>
{question.q}
</div>
{
question.hidden ?
null :
<div id={i} className='white paragraph'>
<br/>
{question.a}
</div>
}
</div>
)
})
}
</div>
</div>
</Layout>
</div>
)
}
Im using Gatsby and react hooks.
Im trying to build a collapsible menu (an faq) sort of like a menu, so that when you click on one of the options, it expands and shows you the answer associated with the question. However, I'm having trouble making it work in real time. whenever i click on the option, my data updates, but the dom itself doesnt update when i click on the option. then, when i change something in the code, the app updates (hot reloader?) and then the dom updates. As far as i understand it, when i change state in real time, the dom should also update in real time, but I can't understand why its not in this case. Has anyone experienced something like this before?
You should not be updating state directly. This should be your toggle code
const toggleItemOpenAndClose = (e) => {
// This will create a new array and with all new objects to preserve immutability
let newArray = arrayOfQuestions.map(x => {
return {... x}
}
newArray[e.target.id].hidden = !newArray[e.target.id].hidden
setArrayOfQuestions(newArray)
}
Make a copy arrayOfQuestions like so,
let array = [...arrayOfQuestions]
Why ?
What you're updating is an object property but it's still belongs to the same array ref. When you spread over the array, you will unpack the object references in your array and pack them in a new array due to those [] and then setting this new array will actually trigger the render.
In case you want to make copy of the objects as well in your array, use map as suggested by #Doug Hill which I think looks better.
But in your case simply spreading will also work for now. Things get messy when there are nesting depths of objects and then consider using libraries which supply you with deep cloning utilities.

disable react css transition group on certain events

I currently have a dashboard type app that pulls messages out of multiple slack channels and displays the ones without any replies or emojis. You can change which channel you want to view.
Currently, the parent state looks something like this:
state = {
selected_channel: window.localStorage.getItem( 'last-slack-ch' ) || 'ABC123'
selected_date: new Date(),
channels: {},
items: [],
slack_users: {},
settings: {
seconds_per_slack_messages_pull: 1
},
atBottom: false
}
After pulling in the messages... or items, I pass that array into a component ItemsContainer.
This is the render method in ItemsContainer
render() {
return (
<div className="items-container">
{
this.props.items.length > 0 ?
<ReactCSSTransitionGroup
transitionName='item'
transitionEnterTimeout={500}
transitionLeaveTimeout={500}>
{
this.props.items.map( t => {
return (
<Item
key={ t.usr + '_' + t.ts }
task={ t }
user={ this.props.slack_users[ t.usr ] }
settings={ this.props.settings }
/>
)
} )
}
</ReactCSSTransitionGroup>
:
<p className='nothing-found'>No items were found in channel: { this.props.selected_channel }</p>
}
</div>
);
}
The issue I am currently facing is that there are certain events that I don't want to have a transition. Stuff like: Initial page load and switching channels.
I tried passing in some props to ItemsContainer which will determine what transitionName to the ReactCSSTransitionGroup... but things didnt really work too well.
Anyone have experience with this?
It's hard to tell from your code exactly how you would implement it, but you could add a key property to the top-level div in your ItemsContainer component. The key property is usually used to identify children in collections but can be used outside of those cases. When a key changes, React creates a new component instance rather than re-render the existing (https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key).
So try re-writing ItemsContainer as:
render () {
return (
<div className="items-container" key={this.props.selected_channel}>
// Transition code that will only apply within the selected channel.
</div>
);
}
This should solve the switching-channel problem. So when ItemsContainer receives a new props.selected_channel value you should avoid the transition animations.

React- onClick styling of currentTarget

I am trying to build a simple dynamically updated, interactive list that styles each <li></li> according to the css rules of a .clicked class, when you click on them.
The app is composed of two components, a parent and a child and the code in question is the following (taken from the child):
handleClick(e) {
document.getElementById(e.currentTarget.id).setAttribute("class","clicked");
}
render() {
let ar = this.props.sentences;
let pro = ar.map((x,i)=>{ return (<li id={i} key={i} className={i%2==0 ? "white" : "grey"}
onClick={this.handleClick}>{x}</li>); })
return (
<div>
<ul id="ul">{ pro }</ul>
</div>
What is happening here is basically that the parent is passing to the child a sentences prop (an array of sentences that will form the basis for the formation of a dynamic list).
The controversial part is me using DOM manipulation in the form of document.getElementById(e.currentTarget.id).setAttribute("class","two");
in order to change the class of the dynamically created html from jsx.
The code above works, however it does not feel as best practice. The whole advantage in using react is to use virtual dom and optimize the way the DOM is updated.
My questions are the following:
1) Am I right to feel this way? (that my solution is not best practice?)
2) (If so, ) How can I structure my code in order to use the virtual dom machinery react offers?
If you know this question to be a duplicate, please leave a comment and I ll remove it.
1) Am I right to feel this way? (that my solution is not best practice?)
It is correct to assume that this is not an ideal approach, manipulating the DOM via vanilla js in React has its place (Example Use Cases) but should not be done unless absolutely necessary. Also, it is not ideal to use the index from Array.prototype.map as the key on your components as if they change order it can cause confusion for React as the keys would map differently in that case.
2) (If so, ) How can I structure my code in order to use the virtual dom machinery react offers?
You should make use of the component state. If you want each clicked element to maintain the clicked class then make a piece of state that caches the elements that have already recieved the clicked class. if only the most recently clicked element gets the clicked class then simply cache an identifier to the appropriate element in the state. You could also use refs for this purpose though the overusage of them is somewhat discouraged by facebook.
Here is a quick snipped that will toggle the click class on each <li>
class Test extends Component {
constructor() {
super();
this.state = {
clicked: {}
};
}
render() {
let ar = this.props.sentences;
let pro = ar.map((x, i) => {
const color_class = i % 2 === 0 ? "white" : "grey";
const clicked_class = this.state.clicked[i] === true ? "clicked" : "";
let clicked = Object.assign({}, this.state.clicked); // Dont mutate state!!!
return (
<li
id={i}
key={i}
className={`${color_class} ${clicked_class}`}
onClick={e => {
if (clicked.hasOwnProperty(i)) {
delete clicked[i];
} else {
clicked[i] = true;
}
this.setState({ clicked });
}}
>
{x}
</li>
);
});
return (
<div>
<ul id="ul">
{pro}
</ul>
</div>
);
}
}

How to setState to one particular component instance in RefluxJS?

I'm currently doing one small project in React + Flux (RefluxJS) and I faced wit one issue I can't solve. I would be very gratefully if someone of you can give me a hand.
Here you have the link to GitHub with the whole project in order to facilitate your help, it's a simplest version just to reproduce the problem I faced.
My doubt is, how can I use one component in the same view with different content. Let me explain:
I have on component in, "components/threads.jsx" which in summary render this peace of code getting the data from the store ("stores/thread-store.jsx") throug a fake API ("utils/api.jsx"):
renderThreads: function() {
return this.state.thread_content.map(function(thread, i) {
return (
<div key={thread.id}>
<div className="row">
<div className="col-md-10">
<a data-toggle="collapse" href={'#thread-' + thread.id} className="faq-question">{thread.name}</a>: <small>{thread.content}</small>
</div>
</div>
<div className="row">
<div className="col-lg-12">
<div id={'thread-' + thread.id} className="panel-collapse collapse ">
<Posts id={thread.id} />
</div>
</div>
</div>
</div>
);
});
},
As yo can see, I have another component nested called "Posts" in "components/thread-posts.jsx" which is rendered for each thread is mapped. In the "Posts" component I have this peace of code:
module.exports = React.createClass({
mixins: [
Reflux.listenTo(PostsStore, 'onChange'),
],
getInitialState: function() {
return {
posts: []
}
},
componentDidMount: function() {
Actions.getPosts(this.props.id);
},
render: function() {
return (
<div>
{this.renderPosts()}
</div>
);
},
renderPosts: function() {
if(this.state.posts.comments != undefined){
return this.state.posts.comments.map(function(post, i) {
return(
<div key={i} className="faq-answer">
<p>
{post.content}
</p>
</div>
);
});
}
},
onChange: function(event, posts) {
this.setState({
posts: posts,
});
}
Here comes the problem. When finish the render, all the threads have the same posts, in particular the lasts ones were set. I think is related with the states, if they change it will be change in all the components were rendered.
So, my question is, how can I deal with it in order to have the posts according to its thread but using the same component? If it's not posible, which is the best solution to do that?
I hope explained myself as well as enough to understand me.
I will be very gratefully if you can give me a hand in this issue.
Thanks in advance.
Yes if you share the Store with your View then latest one will be overwrite your previous Component's data
you have to create a separate variable which can hold the data of each API Call and when call API you have to pass the Key like
let's take one example
I have one Component it's called MainComponent
I want to use same Component on same page twice but the data should be different for both component
I have one Store for MainComponent called MainStore
In MainStore I have one Method called getData like
getData:function(key)
{
//API Call
}
now I am calling this method from my MainComponent from componentDidMount event like
componentDidMount:function()
{
//if you used Action then you have to called like
MainComponentAction.getData(this.props.Key);
// if you directly called
MainComponentStore.getData(this.props.Key);
}
here this.props.Key you have to pass from the parent component which should 1 or 2
now come to store we have passed the Key so now we have to check condition while we received a data from API
suppose I have taken one variable in which I am storing the data which is return by API like
now you have to create two methods for store the data based on key
var _data,_data1
function loaddata(APIdata,key)
{
if(key===1)
{
_data=APIdata
}
else
{
_data1=APIdata
}
}
and in your store you to methods for getting the data like
getData:function()
{
return _data;
},
getData1:function()
{
return _data1;
}
now your getintialState of MainComponent Should be
getintialState:function()
{
return{
Data:JSON.Parse(MainComponentStore.getData()),
Data1:JSON.Parse(MainComponentStore.getData1()),
}
}
and your MainComponent render function should be
render:function(){
var LatestData;
if(this.props.Key===1)
{
LatestData=this.state.Data
}
else if(this.props.Key===2)
{
LatestData=this.state.Data1
}
return(
<div>
{LatestData}
</div>
)
}

How to pass a state className variable to another component in react

I'm not sure if this is the best way to do things but i want to pass one class name variable to another component in react.
This is a button which launch an animation onClick just adding one className to few elements of it. Also i created the var = overlay this.state.cliked ? 'open' : '' to launch an overlay, if i have the overlay html on the same component it works fine but i have to do little components as i can.
var React = require('react');
var OverlayView = require('./OverlayView.jsx');
var StatusBarButtonView = React.createClass({
getInitialState: function() {
return {cliked: false};
},
handleClick: function(event) {
this.setState({cliked: !this.state.cliked});
},
render: function() {
var fondo = this.state.cliked ? 'active' : '';
var overlay = this.state.cliked ? 'open' : '';
return (
<div>
<div className={"statusbar-button-container " + (fondo)} onClick={this.handleClick}>
<img src="images/navbar-element-icon-cross.png" className={"rotate " + (fondo)}/>
</div>
</div>
<OverlayView className={overlay} />
);
}
});
module.exports = StatusBarButtonView;
As you see the is the component of the overlay i want to pass to this component but im not sure if it can just be alone and be launched when this one handle the click. im a bit lost with react, not so much online info and im new with this.
This is the Overlay component:
var React = require('react');
var OverlayView = React.createClass({
return (
<div className={"overlay overlay-slidedown " + this.props.class}>
<nav>
<ul>
<li>Home</li>
<li>About</li>
<li>Work</li>
<li>Clients</li>
<li>Contact</li>
</ul>
</nav>
</div>
);
}
});
module.exports = OverlayView;
I'm not sure how to do this, im looking for examples around the web but nothing very clear for me :(
Use this:
<div className={`statusbar-button-container ${fondo}`} onClick={this.handleClick}>
Note: Make difference between ' and ` (known as backticks). This sign on keyboard is just left to 1 and above tab.
Passing class names as strings between components and even appending class names in the same component is error-prone and not ideal. I'd suggest using the classSet() helper: https://facebook.github.io/react/docs/class-name-manipulation.html
In your case, instead of passing a class prop to the OverlayView component, you should ideally pass a prop that describes the state of the component. Within the OverlayView component, compute the correct classes to be applied using the classSet helper.
For example, instead of using this:
<OverlayView className={overlay} />
you could simply pass in the state variable:
<OverlayView isOpen={this.state.cliked} />
In your OverlayView component, you would then create a classes object using the className helper:
var classes = cx({
'overlay': true,
'overlay-slidedown': true,
'open': this.props.isOpen
});
And change the line in your render() function to use the classes object:
...
<div className={classes}>
...
I tried this part of your code...
return (
<div className={"overlay overlay-slidedown " + this.props.class}>
);
And it seemed to work perfectly for me. It solved my problem: a prop failing to interpolate when I want to display it next to some static text.
I find this better than the accepted answer, because that solved the problem by parameterizing the extra concat'd values, when this is often not desired and against general encapsulation philosophy.
I find it much neater to place the classes in an array, and then reference that:
const styles = [
"container",
"px-4",
"h-1/3",
this.props.class
]
return (
<div className={styles.join(" ")}>
Hello!
</div>
)

Resources