How to use a method in the Main component on another - reactjs

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.

Related

React: is this an acceptable way to add event listeners to a functional component?

i'm working on my first app using react and i want to attach a event listener to a component. I know theres a lot of ways to do this but i came up with this one:
function loginHandler () {
alert('Hey, you clicked me!');
}
const Header = () => {
return (
<header className="header">
<nav className="home-navbar">
<a className="home-navbar__link home-navbar__logo" href="/">Notepp</a>
<a className="home-navbar__link" href="/">About</a>
<a onClick={loginHandler} className="home-navbar__link home-navbar__login" href="/">Sign in with Google</a>
</nav>
</header>
)
}
I don't want to transform this into a class component and create loginHandler method because this is just a simple nav component and i feel like it would somehow complicate thinks a bit. I'm also not sure on how to use hooks although i heard they are very useful. Of course i'm would separate my loginHandler function into her own file but i just want to hear from more experienced developers first. Now this being said i have actually 2 questions.
Is this okay to do?
On the lessons i took from React, the teachers always used class components and onClick to pass event listeners to components, and i decided to create my own app before learning hooks so...
Should i use onClick to handle events?
People always say its best to separate markup from interactivity and i agree with them, and seeing as onClick is at its core (i think) an HTML event handler, i'm a bit suspicious of them (don't wanna hurt my imaginary good practice points right?). Thanks in advance for your help.
Of course, your code is fine, React itself encourages using functional components (with the addition of hooks, memo, ...).
This is almost an exact copy of the code in the official React documentation for Handling Events:
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
I'd suggest at least going through "Main concepts" of the official documentation, before developing your first application. It also has a section on Function and Class Components.
The only remark I could make about this code is that if the onClick will take you to a different screen, then you might look at something like react-router so the URL changes as well.

Why won't my app render when state changes?

I'm building an application that allows for posting community concerns with the ability to upvote those concerns using React, and right now, I am working on the upvote functionality. One way I'm trying to limit the currently logged in user to a single vote is to disable the button once an upvote has been successfully registered for any given post.
To do this, I created a function that checks if the logged in user's ID matches the ID of the upvote for each post. If no match is found, this means the user hasn't voted for already and can register the new upvote. Once this is complete, the button is disabled. I created state for this and is set to false upon the initial render (not sure if this is what I should be doing). I also created state for the all of the votes that have been successfully registered. Both are included below.
const [alreadyVoted, setAlreadyVoted] = useState(false);
const [userVotes, setUserVotes] = useState(upvotes) // upvotes is being passed via props
I'm using the useEffect hook (again, not sure if this is the best way) to check if each button should be enabled or disabled like so:
useEffect(() => {
hasVoted()
}, [userVotes])
Finally, my hasVoted function checks to see if the user has already voted for the issue before and determines the state. It looks like:
function hasVoted() {
userVotes.forEach(vote => {
if (vote.issue_id === issue.id) { // issue here is from props
setAlreadyVoted(true) // I want this to then disable my button
}
})
}
Right now, when I click the button to register the upvote, the page doesn't rerender upon clicking. However, if I refresh the page, the button is successfully disabled as it should be. It probably goes without saying, I'm still getting the hang of React, but any and all help is appreciated.
This code is meaningless at first place
userVotes.forEach(vote => {
if (vote.issue_id === issue.id) { // issue here is from props
setAlreadyVoted(true) // I want this to then disable my button
}
})
, it can be transformed into
if (userVotes.some(vote => vote.issue_id === issue.id)) setAlreadyVoted(true)
but still the logic of it is not clear
I think hasVoted function should be in the useEffect scope. If it's outside, it might introduce bugs, as documented here: https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies
You could try if this helps to resolve your problem
you seem to be somewhat on the right track here but your problem may lay elsewhere,provided the props are passed correctly,because the application does re-render when the state changes. if you want to just disable button you can just use simple html attribute to do that
{alreadyVoted ? (
<button class="btn btn-success" disabled="disabled">
Upvote
</button>
) : (
<button
class="btn btn-success"
onClick={() =>
// Add the id to the setUserVotes here
setAlreadyVoted(true)}
>
Upvote
</button>
)}
Take a look at this codesandbox that i just created and get back to me if you have any issues still

passing mapped data from an api to another component

so i have data that i get from an api in componentDidMount, and then i map over it in the render. lets say the map returns 4 objects. How do i create a button click event that captures that specific objects data, and passes it along a route to another component?
code:
clickEvent(){
???
}
this.example = this.state.data.slice(1).map((data, key) =>
<div key={item.Id}>
<div>{data.dataIWantToPass}</div>
<Link to='/next_component_path' onClick={clickEvent}}>Click</Link>
</div>
So lets say the above returns 4 objects. I want the third one, when the link is clicked to pass its data.
clickEvent(dataUWantToPass){
<NewComponent dataPassed={dataUWantToPass} />
}
this.example = this.state.data.slice(1).map((data, key) =>
<div key={data.Id}>
<div>{data.dataIWantToPass}</div>
//Link is a react-router-dom component, this helps you to redirect to other component (to which you have added the route for
<Link to='/next_component_path'>
<button onClick={()=>this.clickEvent(data.dataIWantToPass)} value="Click"/>
</Link>
</div>
))
You can receive the data in the NewComponent as props.
If you want to check than you can write the componentWillReceiveProps() method in the NewComponent as:
componentWillReceiveProps(reveivedProps){
console.log(reveivedProps);
}
Ok, I have solved this by piecing together various bits from comments, so thank you for your contributions. This is how it was resolved:
On the link tag i did this:
<Link to={{ pathname: '/path', query:{passed_id: data.id} }}></Link>
Then on the component that the path routes to:
this.setState({passed_id: this.props.location.query.passed_id});
And that gave me access to the data i was passing, in this case an ID. Now I just need to figure out how to compare that ID, with the looped data I also pass (via local storage), and map over it. Another question i suppose.
You need to use redux. This allows you to store data in global application store, and get the data from any component which is subscribed to the store. Today almost every react project needs redux to manage the data across the application

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.

How to Jump to Redux State with Browser Back Button

I’m a react beginner and I have a project using React Router (4.2.2), Redux and I’ve recently added react-router-redux hoping that it would solve my problem. I have components whose redux stored states need to change based on interactions with the browser back and forward buttons.
For example, I have a screen containing details for an element the user clicked. If I use the browser back button and navigate to another point in history in which that details view was open, it will only show the most recent element info (due to that the only information in the store) or sometimes no information at all will be passed.
I thought react-router-redux would help me keep these two in sync but maybe I’m missing a step that enables that to happen. I’ve installed Redux Debug Tools and I can see in there the state that I want to jump to, but how do I enable it when the user uses the back button? The url for the details page is the same each time it’s viewed, perhaps I need to add a hash tag with specific information but even then, how would I do a look up for the correct information in the store?
I open the details view like this:
<Link to='/activityDetails'>
<ActivityButton
id={this.props.someData.someId}
/>
</Link>
And inside ActivityButton:
openActivityDetails = () => {
this.props.showActivityDetailsScreen(this.props.id);
};
function matchDispatchToProps(dispatch) {
return bindActionCreators({
showActivityDetailsScreen: activityDetailsActions.showActivityDetails
}, dispatch)
}
render(){
return (
<div className={'activity-button'} onClick={this.openActivityDetails}>
<img
onClick={this.handleClick}
src={imgPath}
/>
</div>
);
}
So I figured it out on my own. Essentially what you need to do is set the state information inside the when you create it, like so:
<Link to={{
pathname: '/activityDetails',
state: { "activityData": activityData }
}}>
<ActivityButton
id={this.props.someData.someId}
/>
</Link>
then in the class you need the information ActivityDetails in my case you can query it like this:
var actData = this.props.location.state.activityData;
or if you're using withRouter and history it's also in the following location:
const { history: { push } } = this.props;
history.state.state.activityData
I ended up removing react-router-redux because it wasn't necessary. It made an entry in the state with the current route, but in my case that wasn't useful. Hope this helps someone in the future.

Resources