Do logic on props after fetch with Redux action - reactjs

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.

Related

Conditional rendering with useEffect / useState, why is there a delay in the DOM when switching components?

Intro / Context
I am trying to build an application using React that allows for image or video display based on a chosen menu item. I am currently using Advanced Custom Fields within WordPress to build my data objects and using graphQL to query them into my project.
I want the application to display either a video component or an image component. This choice will be determined through conditional rendering and based on the contents of the object's fields.
Each object contains a title, an image field and a video field. If the entry in question should be displayed as an image the video field will be set as the string 'null'. All fields will return strings regardless.
I am using useState to target a particularly active field, however, despite triggering a change in state conditional rendering does not appear to change.
The Application
This is my approach
function Display({ objects }) {
const [setVisualOption, changeVisualOption] = useState(false);
const [appState, setState] = useState({
myObjects: objects,
activeTitle: "null",
activeImage: "null",
activeMediaUrl: "null",
});
function toggleActive(index, trackIndex) {
setState({
...appState,
activeTitle: appState.myObjects[index].title,
activeImage: appState.myObjects[index].image[0].mediaItemUrl,
activeMediaUrl: appState.myObjects[index].mediastreamurl,
});
changeVisualOption(appState.activeImage.includes("null"));
}
useEffect(() => {}, [
appState.activeTitle,
appState.activeImage,
appState.activeMediaUrl,
setVisualOption,
]);
return (
<div className="display">
<div className="list-box-right>
{appState.myObjects.map((element, index) => (
<>
<div
key={index}
className="menu-item"
onClick={() => {
toggleActive(index);
}}
>
{element.title}
</div>
</>
))}
</div>
<div className="right-grid">
{setVisualOption ? (
<VideoComponent activeImage={appState.activeImage}></VideoComponent>
) : (
<ImageComponent activeImage={appState.activeImage}></SingleImage>
)}
</div>
</div>
);
}
The summarise, to component takes objects as prop which are being passed down from another component making the graphQL query. I am then setting the initial values of useState as an object and setting an activeTitle, activeImage and activeMediaUrl as null.
I am then using a function to toggle the active items using the setState modifier based upon the index that is clicked within the return statement. From there I am using setVisualOption and evaluating whether the activeImage is contains 'null' (null.jpg), if this is true setVisualOption will be set to true allowing the Video Component to be rendered
The Problem
To be clear, there are no errors being produced and the problem is a slight rendering issue where it requires double clicks to alter the state and trigger the correct response from the tertiary operator.
The issue is within the conditional rendering. If I set my object fields to all images and return only the Image Component there are no issues, and the state change can be seen to register visually as you click down the listed options.
It is only when I introduce the conditional rendering that there is a delay, the first click does not generate the correct response from the component I am trying to display however the second click triggers the right response.
As you can see, I am also using useEffect to try and trigger a rendered response when any of the described states change but still encounter the same problem.
Does anyone know what is the cause of this bug? when looking at the console.log of setVisualOption is not appearing as true on first click when it aught to.
Any insights would be great thanks
You set your visual option right after you set your appState, this is why appState.activeImage in changeVisualOption is not updated because state updates in React is asynchronous. You can either use useEffect to update visual option when the appState changes or you can use appState.myObjects[index].image[0].mediaItemUrl in changeVisualOption
function toggleActive(index, trackIndex) {
setState({
...appState,
activeTitle: appState.myObjects[index].title,
activeImage: appState.myObjects[index].image[0].mediaItemUrl,
activeMediaUrl: appState.myObjects[index].mediastreamurl,
})
changeVisualOption(appState.myObjects[index].image[0].mediaItemUrl.includes("null"))
}
or
useEffect(() => {
changeVisualOption(appState.activeImage.includes("null"))
}, [appState])

How to re-render a parent from a children component with hook?

I would like to force my parent to re-render the page when I click on a button from a child component.
(I don't use redux because I don't have the time to learn it in my project, so I use localStorage. Unfortunately React don't see when a change is operated on local Storage, so he don't re-render. It's why I would like to force it to re-render my page (to have the right content).)
I tried to use hook with the function useState to do it but it's not working and I don't know why...
(Nothing change in my page)
This is my parent page: (just the code important)
const[reload, setReload] = useState(false);
...
else if (user) { contents = [<Message_UserIdentified user={user} callBack={setReload}/>, contentform]; }
This is my child component:
const Message_UserIdentified = (props) => {
let user = props.user;
return (
<Alert color="primary" className="alert alert-dismissible alert-info">
<h4>Welcome {!user ? "" : user.firstname} {!user ? "" : user.lastname}</h4>
If you are not {!user ? "" : user.firstname} click <a onClick={() => {localStorage.removeItem('idUser'); props.callBack(true);}}>here.</a>
</Alert>
);
}
Why my parent page don't want re-render ?
Thanks in advance.
I have created a proof of concept of what you are trying to achieve and it works:
https://codesandbox.io/s/weathered-smoke-ojr5j
probably there's something else in your code that we can't see that's preventing the component to re render
Your child component can have a prop which directly pass setReload to it.
However one common usage is that, setReload can be associated with an event, ex. onReload. You can pass a prop onReload to the child instead.
<Child onReload={() => { setReload() }} />
Inside onReload implementation, you can call setReload.
The reload state variable in your parent component is strictly local to it; the child can't see it.
I've been using React Hooks for about 2 months now. The learning curve, at times, has been steep but I'm now getting really proficient at it.
A companion technology to Hooks called Context API is perfect for your needs. It's what you should be using rather than local storage because both components can access it. Your child component would set the equivalent of reload in the Context to true and your parent would have a useEffect function that would have reload as a dependency. Thus, when reload is changed from false to true, the useEffect function in the parent would be fired and run the code you desire.
Early on, I very much benefitted from this video series: https://www.youtube.com/watch?v=6RhOzQciVwI&t=46s Watch the first few videos and you should quickly understand how to implement the Context API in your functional React components.

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 do I call an event handler or method in a child component from a parent?

I'm trying to implement something similar to the Floating Action Button (FAB) in the Material-UI docs:
https://material-ui.com/demos/buttons/#floating-action-buttons
They have something like:
<SwipeableViews>
<TabContainer dir={theme.direction}>Item One</TabContainer>
<TabContainer dir={theme.direction}>Item Two</TabContainer>
<TabContainer dir={theme.direction}>Item Three</TabContainer>
</SwipeableViews>
{
fabs.map((fab, index) => (
<Zoom>
<Fab>{fab.icon}</Fab>
</Zoom>
));
}
I have something like:
<SwipeableViews>
<TabContainer dir={theme.direction}>
<ListOfThingsComponent />
</TabContainer>
<TabContainer dir={theme.direction}>Item Two</TabContainer>
<TabContainer dir={theme.direction}>Item Three</TabContainer>
</SwipeableViews>
{
fabs.map((fab, index) => (
<Zoom>
<Fab onClick={ListOfThingsComponent.Add???}>
Add Item to List Component
</Fab>
</Zoom>
));
}
My ListOfThingsComponent originally had an Add button and it worked great. But I wanted to follow the FAB approach for it like they had in the docs. In order to do this, the Add button would then reside outside of the child component. So how do I get a button from the parent to call the Add method of the child component?
I'm not sure how to actually implement the Add Item to List click event handler given that my list component is inside the tab, while the FAB is outside the whole tab structure.
As far as I know I can either:
find a way to connect parent/child to pass the event handler through the levels (e.g. How to pass an event handler to a child component in React)
find a way to better compose components/hierarchy to put the responsibility at the right level (e.g. remove the component and put it in the same file with this in scope using function components?)
I've seen people use ref but that just feels hacky. I'd like to know how it should be done in React. It would be nice if the example went just a bit further and showed where the event handling should reside for the FABs.
thanks in advance, as always, I'll post what I end up doing
It depends on what you expect the clicks to do. Will they only change the state of the given item or will they perform changes outside of that hierarchy? Will a fab be present in every single Tab or you're not sure?
I would think in most cases you're better off doing what you were doing before. Write a CustomComponent for each Tab and have it handle the FAB by itself. The only case in which this could be a bad approach is if you know beforehand that the FAB's callback will make changes up and out of the CustomComponent hierarchy, because in that case you may end up with a callback mess in the long run (still, nothing that global state management couldn't fix).
Edit after your edit: Having a button call a function that is inside a child component is arguably impossible to do in React (without resorting to Refs or other mechanisms that avoid React entirely) because of its one-way data flow. That function has to be somewhere in common, in this case in the component that mounts the button and the ListOfThings component. The button would call that method which would change the state in the "Parent" component, and the new state gets passed to the ListOfThings component via props:
export default class Parent extends Component {
state = {
list: []
};
clickHandler = () => {
// Update state however you need
this.setState({
list: [...this.state.list, 'newItem']
});
}
render() {
return (
<div>
<SwipeableViews>
<TabContainer dir={theme.direction}>
<ListOfThingsComponent list={this.state.list /* Passing the state as prop */}/>
</TabContainer>
<TabContainer dir={theme.direction}>Item Two</TabContainer>
<TabContainer dir={theme.direction}>Item Three</TabContainer>
</SwipeableViews>
{
fabs.map((fab, index) => (
<Zoom>
<Fab onClick={this.clickHandler /* Passing the click callback */}>
Add Item to List Component
</Fab>
</Zoom>
))
}
</div>
)
}
}
If you truly need your hierarchy to stay like that, you have to use this method or some form of global state management that the ListOfThingsComponent can read from.

ReactJS + Flux setState not working on single click

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

Resources