ReactJS + Flux setState not working on single click - reactjs

I'm writing my first reactjs + flux application and I have managed to get the basic code working. However, there is a list, which populates another list depending on which list item is clicked. This was working fine until I had my list data hard-coded in the store. But when I started getting the data for the list items from the server, the second list does not get populated on a single click - it needs two clicks to do that. I don't think that getting data from server is a problem because the default data is populated correctly. After debugging, I found out that the store is also returning the data correctly, but the setState() is not setting the data. What am I doing wrong?
Here's my code:
function getInitialData(){
return{
items:DataStore.getFirstItemData()
}
}
var ListItems = React.createClass({
getInitialState:function(){
return getInitialData();
},
_onChange:function(){
this.setState(getInitialData);
},
componentDidMount:function(){
DataStore.addChangeListener(this._onChange);
},
componentWillReceiveProps:function(){
this.setState({items:this.props.itemsData});
},
handleClick:function(e)
{
var data = DataStore.getSubItemsFor(e);
this.setState({items:data});
},
render:function(){
return(
<div>
<div className="col-md-3 dc-left" id="main-list">
<div className="dc-main-list-parent">
{this.props.listData.map(function(list_items){
return(
<div key={list_items.id} onClick = {this.handleClick.bind(this,list_items.id)}>
</div>
);
},this)}
</div>
</div>
<div className="col-md-9 dc-center" id="sub-list">
<SubList sublistData = {this.state.items} />
</div>
</div>
)
}
});
P.S. This code has been modified a little for posting here. Please overlook any syntactical errors - it works!

Probably things break down because updates to the DataStore trigger 2 changes:
The complete component gets rerendered by the parent component, which fires the componentWillReceiveProps + fires a render cycle.
You also have a listener to changes in the DataStore, so the update in the DataStore also fires the _onChange event, which tries to update the state as well
PS: the code has a typo:
this.setState(getInitialData);
should read:
this.setState(getInitialData());
I would suggest you remove the change listener part, and see if that helps..

Related

Do logic on props after fetch with Redux action

i recently started to work with Redux in React.
I was wondering how to do some logic on data which i've just fetched (is it a bad practice?), in the same handler were i call the redux actions.
I attach a simple example so you can understand what i mean.
function CakeContainer(props) {
return (
<div>
<h2>Number of cakes - {props.numOfCakes}</h2>
<button onClick={props.buyCake}>Buy Cake</button>
</div>
)
}
props.buyCake works fine, it fetches numOfCakes from the redux state and decreases by 1 the value, which is displayed in the component.
So if the value of numOfCakes at the beginning is 10, after click on the button, it will be 9.
What if i want to, for example, multiply the value of numOfCake inside the button handler?
The component code will be
function CakeContainer(props) {
return (
<div>
<h2>Number of cakes - {props.numOfCakes}</h2>
<button onClick={() => {
props.buyCake();
console.log("num multiplied by 2",props.numOfCakes*2)
}}>Buy Cake</button>
</div>
)
}
In the console log, after the first click on the button i have 20 (10*2) because NumOfCakes is not updated yet.
Is there a way to have the updated value in the handler or is it a bad practice?
You can pass a callback to the redux action in which you can get your latest state value. Here's a codesandbox for your use case
https://codesandbox.io/s/reduxexample-forked-dowuj
Alternatively, you could return a promise from your action and resolve the same to your cakeContainer.

How to use a method in the Main component on another

I am trying to save notes into localStorage (or in this case localforage). I have a simple text area with a button called "save." The save button is located in another file indicated below.
I used an example found here to try to set the items.
This is the code I wrote:
SaveMessage() {
var message = <Notepad></Notepad>;
reactLocalforage
.SetItem("Message", message, function(message) {
console.log(message);
})
.catch(function(err) {
console.log(err);
});
}
The var message is something I'm not too sure of either. Notepad is another component with the code which contains the text area and buttons:
<div className="button-container">
<button
className="save-button"
onClick={() => {
props.onSaveMessage(saveMessage);
}}
>
Save
</button>
<button className="remove-button">Show all</button>
</div>
The area where it says onClick I was hoping there would be a way to use the SaveMessage method with localforage initially I tried creating it as a prop (from a tutorial) so in the main method I'd have:
render() {
return (
<div>
<Notepad onSaveMessage={this.SaveMessage}></Notepad>
</div>
);
}
and then on the Notepad component:
<button
className="save-button"
onClick={() => {
props.onSaveMessage();
}}
>
Save
</button>
When I click the save button on my application I am hoping something will be set within the local-storage on the browser, but I get an exception:
TypeError: Cannot call a class as a function
The error occurs when I set item on the save message code above and when I try to call it as a prop onSaveMessage(saveMessage).
You haven't added enough code to be able to fix your exact issue, but I can help you understand how you should proceed to fix it. In React when you want to share information between components, you need to lift that data into a parent component, and then share it through props. If your component tree is profound, then this task can get tedious. So several libraries have been developed to manage your app's state.
I suggest you start by reading "Lifting State Up" from the React docs page. To help you apply these concepts to your current situation, I've created a CodeSandbox in which I try to replicate your situation. You can find it here.
Once you understand the need to "lift" your state, and how you can share both data and actions through props you can migrate to state handler tool. Some of them are:
React Context
Redux
MobX
There are much more. It is not an extensive list, nor the best, just the ones I have used and can vouch that they can help you.
I hope this helps.

How to wait and fade an element out?

I have an alert box to confirm that the user has successfully subscribed:
<div className="alert alert-success">
<strong>Success!</strong> Thank you for subscribing!
</div>
When a user sends an email, I'm changing the "subscribed" state to true.
What I want is to:
Show the alert box when the subscribed state is true
Wait for 2 seconds
Make it fade out
How can I do this?
May 2021 update: as tolga and Alexey Nikonov correctly noted in their answers, it’s possible to give away control over how long the alert is being shown (in the original question, 2 seconds) to the transition-delay property and a smart component state management based on the transitionend DOM event. Also, hooks are these days recommended to handle component’s internal state, not setState. So I updated my answer a bit:
function App(props) {
const [isShowingAlert, setShowingAlert] = React.useState(false);
return (
<div>
<div
className={`alert alert-success ${isShowingAlert ? 'alert-shown' : 'alert-hidden'}`}
onTransitionEnd={() => setShowingAlert(false)}
>
<strong>Success!</strong> Thank you for subscribing!
</div>
<button onClick={() => setShowingAlert(true)}>
Show alert
</button>
(and other children)
</div>
);
}
The delay is then specified in the alert-hidden class in CSS:
.alert-hidden {
opacity: 0;
transition: all 250ms linear 2s; // <- the last value defines transition-delay
}
The actual change of isShowingAlert is, in fact, near-instant: from false to true, then immediately from true to false. But because the transition to opacity: 0 is delayed by 2 seconds, the user sees the message for this duration.
Feel free to play around with Codepen with this example.
Since React renders data into DOM, you need to keep a variable that first has one value, and then another, so that the message is first shown and then hidden. You could remove the DOM element directly with jQuery's fadeOut, but manipulating DOM can cause problems.
So, the idea is, you have a certain property that can have one of two values. The closest implementation is a boolean. Since a message box is always in DOM, it's a child of some element. In React, an element is result of rendering a component, and so when you render a component, it can have as many children as you want. So you could add a message box to it.
Next, this component has to have a certain property that you can easily change and be completely sure that, as soon as you change it, the component gets re-rendered with new data. It's component state!
class App extends React.Component {
constructor() {
super();
this.state = {
showingAlert: false
};
}
handleClickShowAlert() {
this.setState({
showingAlert: true
});
setTimeout(() => {
this.setState({
showingAlert: false
});
}, 2000);
}
render() {
return (
<div>
<div className={`alert alert-success ${this.state.showingAlert ? 'alert-shown' : 'alert-hidden'}`}>
<strong>Success!</strong> Thank you for subscribing!
</div>
<button onClick={this.handleClickShowAlert.bind(this)}>
Show alert
</button>
(and other children)
</div>
);
}
}
Here, you can see that, for message box, either alert-shown or alert-hidden classname is set, depending on the value (truthiness) of showingAlert property of component state. You can then use transition CSS property to make hiding/showing appearance smooth.
So, instead of waiting for the user to click button to show the message box, you need to update component state on a certain event, obviously.
That should be good to start with. Next, try to play around with CSS transitions, display and height CSS properties of the message box, to see how it behaves and if the smooth transition happening in these cases.
Good luck!
PS. See a Codepen for that.
The correct way is to use Transition handler for Fade-in/out
In ReactJS there is synthetic event to wait till fade-out is finished: onTransitionEnd.
NOTE there are different css effects associated with different handlers. Fade is a Transition not an Animation effect.
Here is my example:
const Backdrop = () => {
const {isDropped, hideIt} = useContext(BackdropContext);
const [isShown, setState] = useState(true);
const removeItFromDOM = () => {
debugger
setState(false)
};
return isShown
? <div className={`modal-backdrop ${isDropped ? 'show' : ''} fade` } onClick={hideIt} onTransitionEnd={removeItFromDOM}/>
: null
}
An other way is to solve this with a CSS3 transition.
https://www.tutorialspoint.com/css/css_animation_fade_out.htm
You can add a new class to the alert (like .hidden) and then you can relate .hidden with the class you defined for the alert.
alert.hidden{
// Here you can define a css transition
}
In this solution you don't have to add a setInterval or anything, since css3 transitions already process it on browser render.

React component not updating as expected on state

I am building a simple recipes list app and am running into a problem. I am probably misunderstanding something fundamental about how React renders.
Here's some of the code from my codepen
(scroll down to where I explain my code and my problem)
save(action, item){ //this is the save method of my App component
if (action == 'edit'){ // EDIT
...
} else if (action == 'delete'){ // DELETE
let newRecipes = this.state.recipes.filter( function(recipe){ return recipe.key != item.key } ) //this correctly removes the item I want to delete
this.state.recipes = newRecipes; //making extra sure the state is updated
this.setState({ recipes: newRecipes }); //shouldn't this make App render?
this.forceUpdate(); //cmon App, please re-render
console.log('newRecipes', newRecipes);
console.log('main state', this.state); //the state was updated as expected, but App does not show what the state contains
}
Later, in App's render()
<ul className="well list-group">
{
this.state.recipes.map( (recipe)=>{ //i expect the recipe items generated to reflect App's new state, but they dont :(
return(
<Recipe
save={this.save}
original={recipe}
id={recipe.key}
title={recipe.title}
ingredients={recipe.ingredients}
instructions={recipe.instructions}
/>
)
} )
}
</ul>
My app is like this:
I have an App component at the top that renders all the other components and whose state should contain all the data about the App overall.
state.recipes is an array with recipe objects. These are passed as properties to the Recipe component, in the App render method. (<Recipe title=... />)
The <Recipe /> component has methods and buttons to edit and delete the recipe. when a recipe is edited or deleted, the change is handled by the save() method in App, which is also passed to <Recipe /> as a prop.
From my understanding, that is all standard React usage.
This all seems to work as expected. When I edit or delete a recipe the change is properly reflected in the App state.
The Problem
The unexpected behavior happens when deleting a recipe. Even though the correct recipe is removed from App state, that change is not reflected in the rendered HTML. Instead, whatever the last recipe in the App.state.recipes array is removed and I am left staring at a console log of the state and rendered HTML that show different things.
What I've tried:
I expected that using setState({ recipes: newRecipes}) to update App state to the newly updated recipes list and show it on the screen. But this did not seem to work. I even used .forceUpdate() but that did not work.
I added some console logging to Recipe's constructor function and learned that it only runs once in the beginning. It doesn't run again as I would expect when the recipes array in App's state is changed.
You missed to put a key property on Recipe elements, because of that React does not know which items are changing and how to react to that change. If you just put the key property everything will work as expected.
<Recipe
save={this.save}
original={recipe}
id={recipe.key}
key={recipe.key} // <-- HERE
title={recipe.title}
ingredients={recipe.ingredients}
instructions={recipe.instructions}
/>
Remember to add a key property to all the components created from iterations.
See your example working here
http://codepen.io/damianfabian/pen/egeevR

React.js get checked inputs

I am quiet new to React and I wasn't able to figure out how to manipulate DOM.
I have a set of components with checkboxes, and I have a delete button, I want to delete the checked elements when delete button is clicked.
here is a snippet of code I am using :
...
deleteMessage: function(event) {
this.refs.select_message.getDOMNode(); // I get only the last element
},
...
...
render: function() {
var Messages = this.props.messages;
return (
<div>
<button onClick={this.deleteMessage}>Delete</button>
{Messages.map(function(message) {
return (
<div>
<input type='checkbox' className='select_message' ref='select_message'/>
</div>
);
})}
</div>
);
Am I doing it the right way?
What you should do is, in your deleteMessage, call a handler which you have passed from the parent. That, in turn, modifies the messages array from the outside. Then the new messages array will be passed in as props. The main insight you need is props are not only data passed for rendering, but also functions to be called when user interaction happens inside the component.

Resources