disable react css transition group on certain events - reactjs

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.

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.

React Stateless Components: Interacting with their output and appearance

I have looked around for an answer to this - the closest I found being this question - but there is I think a significant difference in my case (the fact that it starts to get into the parent holding the state of its children's... children) which has finally lead to me asking for some clarification.
A very simple example of what I mean is below (and will hopefully better illustrate what I'm asking):
Suppose we have a bunch of book documents like
bookList = [
{
title: "book 1",
author: "bob",
isbn: 1,
chapters: [
{ chapterNum: 1, chapterTitle: "intro", chapterDesc: "very first chapter", startPg: 2, endPg: 23 },
{ chapterNum: 2, chapterTitle: "getting started", chapterDesc: "the basics", startPg: 24, endPg: 45 }
]},
{
title: "book 2" ... }
]
So main point being these embedded objects within documents that could be very long and as such may be collapsed / expanded.
And then here is a rough sample of code showing the components
class App extends Component {
constructor(props) {
super(props);
this.state = {
books: bookList,
focusBook: null
}
this.updateDetailDiv = this.updateDetailDiv.bind(this);
}
updateDetailDiv(book) {
this.setState(
{ focusBook: book}
);
}
render() {
return(
<BookList
bookList = {this.state.books}
updateDetailDiv = { this.updateDetailDiv }
/>
<BookDetail
focusBook = { this.state.focusBook }
/>
);
}
}
const BookList = props => {
return (
props.bookList.map(item=>
<li onClick={()=> props.updateDetailDiv(item)}> {item.title} </li>
)
);
}
const BookDetail = props => {
return (
<div className="bookDetails">
{ props.focusBook != null
? <div>
{props.focusBook.title},
{props.focusBook.author},
{props.focusBook.isbn}
Chapters:
<div className="chapterList">
{ props.focusBook.chapters.map(item=>
<span onClick={()=>someFunction(item)}>{item.chapterNum} - {item.chapterName}</span>
)}
</div>
<div id="chapterDetails">
This text will be replaced with the last clicked chapter's expanded details
</div>
</div>
: <div>
Select A Book
</div>
})
}
someFunction(item) {
document.getElementById('chapterDetails').innerHTML = `<p>${item.chapterDesc}</p><p>${item.startPg}</p><p>${item.endPg}</p>`;
}
So my problem is that i'm not sure what the best approach is for handling simple cosmetic / visual changes to data in functional stateless components without passing it up to the parent component - which is fine and makes sense for the first child - but what happens when many children will have their own children (who may have their own children) --> all requiring their own rendering options?
For example - here the App component will re-render the DetailDiv component (since the state has changed) - but I don't want the App also handling the DetailDiv's detailed div. In my example here its all very simple but the actual application I'm working on has 2 or 3 layers of embedded items that - once rendered by App - could realisticially just be modified visually by normal JS.
SO in my example you'll see I have a someFunction() in each Chapter listing - I can make this work by writing a separate simple 'traditional JS DOM function' (ie: target.getElementById or closest() -- but i don't think i'm supposed to be using normal JS to manipulate the DOM while using React.
So again to summarize - what is the best way to handle simple DOM manipulation to the rendered output of stateless components? Making these into their own class seems like overkill - and having the 'parent' App handle its 'grandchildren' and 'great-grandchildren's state is going to be unwieldy as the Application grows. I must be missing an obvious example out there because I haven't seen much in the way of handling this without layers of stateful components.
EDIT for clarity:
BookDetail is a stateless component.
It is handed an object as a prop by a parent stateful component (App)
When App's state is changed, it will render again, reflecting the changes.
Assume BookDetail is responsible for displaying a lot of data.
I want it so each of the span in BookDetail, when clicked, will display its relevant item in the chapterDetail div.
If another span is clicked, then the chapterDetail div would fill with that item's details. (this is just a simple example - it can be any other pure appearance change to some stateless component - where it seems like overkill for a parent to have to keep track of it)
I don't know how to change the UI/appearance of the stateless component after it is rendered without giving it state OR making the parent keep track of what is essentially a 'substate' (since the only way to update the appearance of a component is to change its state, triggering a render).
Is there a way to do this without making BookDetail a stateful component?
You can add a little bit of simple state to functional components to track the selected index. In this case I would store a "selected chapter index" in state and then render in the div the "chapters[index].details", all without manipulating the DOM which is a React anti-pattern.
The use-case here is that the selected chapter is an internal detail that only BookDetail cares about, so don't lift this "state" to a parent component and since it is also only relevant during the lifetime of BookDetail it is rather unnecessary to store this selected index in an app-wide state management system, like redux.
const BookDetail = ({ focusBook }) => {
// use a state hook to store a selected chapter index
const [selectedChapter, setSelectedChapter] = useState();
useEffect(() => setSelectedChapter(-1), [focusBook]);
if (!focusBook) {
return <div>Select A Book</div>;
}
const { author, chapters, isbn, title } = focusBook;
return (
<div className="bookDetails">
<div>
<div>Title: {title},</div>
<div>Author: {author},</div>
<div>ISBN: {isbn}</div>
Chapters:
<div className="chapterList">
{chapters.map(({chapterName, chapterNum}, index) => (
<button
key={chapterName}
onClick={() => setSelectedChapter(selectedChapter >= 0 ? -1 : index)} // set the selected index
>
{chapterNum} - {chapterName}
</button>
))}
</div>
// if a valid index is selected then render details div with
// chapter details by index
{chapters[selectedChapter] && (
<div id="chapterDetails">
{chapters[selectedChapter].details}
</div>
)}
</div>
</div>
);
};
DEMO
There is some approaches you can do to solve this problem.
First, you don't need to create some class components for your functional components, instead, you can use react hooks, like useState so the component can control it's own content.
Now, if you don't want to use React Hooks, you can use React Redux store to manage all your states: you can only change the state values using the Redux actions.
Happy coding! :D

How to conditionally render extra form options on checking dynamically generated check boxes in React?

can anyone help, i'm trying this in React but i'm really stuck.
I have 3 checkboxes on a child component that i have rendered dynamically using an array of options in state (in the parent component):
What i need to be able to do is, as per the below image: click on each checkbox and get further options.
https://discourse-user-assets.s3.dualstack.us-east-1.amazonaws.com/original/3X/d/3/d385407399b0fa130741e653c9650ff9953282df.png
The checked options and the sub options (not just dropdowns, the third checkbox has input fields) all need to be available in state so i can collect them up and post them to a database
What i have so far is the below:
Array of options in state:
sowType: [
"ProductSow",
"Teradata Customer SOW",
"Custom Professional Services SOW"
]
Final rendered checkboxes:
https://discourse-user-assets.s3.dualstack.us-east-1.amazonaws.com/original/3X/4/5/45702836b84abe764917f4bdf5172258f4d3e39c.png
My problem is, i don't know what to do next. I have dynamically rendered the initial 3 check boxes but i don't know how to add the conditional rendering to get the sub boxes to appear on clicking the check boxes) and add the info selected from them to state.
i.e. can i only add the conditional rendering on checkboxes that have NOT been dynamically rendered from map or is there a way to do it, in which case how is it done?
My code so far is as below, it may not be the best setup for what i am trying to do:
Can anyone help??
This is the reference to the component in the parent with the props passed down:
<SowType
title={"SOW Type"}
setName={"SOW Type"}
subtitle={"What type of SOW do you want to generate?"}
type={"checkbox"}
controlFunc={this.handleSOWTypeCheckbox}
options={this.state.sowType}
selectedOptions={this.state.sowTypeSelectedOption}
/>
This is the child component dynamically rendering the array of items from state:
class SOWType extends React.Component {
render() {
// console.log(this.props);
return (
<div className="form-group">
<label htmlFor={this.props.name} className="form-label">
{this.props.title}
<h6>{this.props.subtitle}</h6>
</label>
<div className="checkbox-group">
{this.props.options.map(option => {
return (
<label key={option}>
<input
className="form-checkbox"
name={this.props.setName}
onChange={this.props.controlFunc}
value={option}
checked={this.props.selectedOptions.indexOf(option) > -1}
type={this.props.type}
/>
{option}
</label>
);
})}
</div>
</div>
);
}
}
This is the method i am currently using in the parent when a checkbox is clicked in the child component (this basically takes the selected option from the array in state and puts the checked options into another array in state called 'sowTypeSelectedOption: [ ]',
handleSOWTypeCheckbox(e) {
const newSelection = e.target.value;
let newSelectionArray;
if (this.state.sowTypeSelectedOption.indexOf(newSelection) > -1) {
newSelectionArray = this.state.sowTypeSelectedOption.filter(
item => item !== newSelection
);
} else {
newSelectionArray = [...this.state.sowTypeSelectedOption, newSelection];
}
this.setState(
{
sowTypeSelectedOption: newSelectionArray
} /*() =>
console.log("sow Type Selection: ", this.state.sowTypeSelectedOption) */
);
}
If I understand your question correct, you want the additional sub menu to show once the checkbox is selected, then you can use conditional rendering. Just show/hide the menu on select or deselect of checkbox like so.
{ this.props.selectedOptions.indexOf(option) > -1 ? (
<h5>submenu options component renders here</h5>
) : " "
}

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

Resources