reusing stateful react components - reactjs

I'm new to React and wanted some advice.
The problem is essentially thew following
I have a number of component buttons that open a modal, within this modal we have further buttons to offer a selection.
Home Screen Buttons (components)
<Button value="First"></button>
<Button value="Second"></button>
<Button value="Third"></button>.....
Modal.
<button value="Donald"></button>
<button value="Thomas"></button>
<button value="Evie"></button>.....
So the home screen buttons for example would have the following function, that it would pass down to the modal buttons onClick attribute.
selectPerson(e) {
setState({ selection : e.target.value})
}
So by selecting "First", we choose a person, close the modal, tie the selected person with Buttons state, and then repeat for second and so on.
Essentially these Buttons to open the modal have the same core functions (state and props). i.e I could have a template component an reuse it, but I would like each component to have independent state and props.
So I can achieve what I need but I've written each home screen button as an independent component, that is I've written a lot of the same code. If I attempt to reuse the SAME component, the are treated as the same component, and selecting a person changes all buttons state.
Is there anyway to avoid rewriting the same code for each (I have twenty). I've only just started (obviously) and am not too familiar with some of the more advanced concepts. If anyone has any suggestions or further questions, it would be great. I haven't provided code as the code works, its just extremely bulky

Yeah sure, define the button components and hand down certain elements inside its props.
You can then setState to be these handed down prop elements etc etc.
So define a button component with its various states defined by the props you hand it down. Then you have a mutable template Button component.
When you render these you just define its props.
<Button prop = "First" prop2 = "EXAMPLE"..../>
<Button prop = "Second" prop2 = "EXAMPLE2"..../>
<Button prop = "Third" prop2 = "EXAMPLE3"..../>
then inside the Button component use something like
this.state { property1 : this.props.prop, property2 : this.props.prop2 ...}
Hope this helps! I've recently been doing something similar

Related

How do I make a selected radio button change one piece of state based upon another radio button changing another piece of state?

I have a couple of components I am building and have run into a bit of a stumbling block.
The components start with a piano keyboard on which I can overlay different musical entities, like notes, intervals, chords, scales, etc...by passing the note names via a notes={[]} array prop into the Keyboard component. In this particular instance of the component I am then importing it into a Scales component in which I have added scale and note buttons, so I can overlay entire scales in this instance on top of the keyboard, which is all basically working. have a list of note names that I can click on and then overlay that scale on top of the keyboard, like so:
The gif shows the current behavior of the Scales component, which is not quite correct yet...
In the gif you can see that the major and minor scale buttons change the state of the scale, which is then used by the note name buttons to change the state of the keyboard beneath it. But what I also want it to do is switch the currently selected scale when the major or minor button is clicked, but currently the note name buttons don't automatically react to the change in state in the major and minor buttons, but have to be clicked again to make the change occur.
What I want is to be able to just click the major and minor buttons and which ever note is selected will simply change from major to minor without having to re-click the selected scale note again, which is the current behavior.
So, in my Scales component I am using custom radio buttons like so, first to control the major and minor buttons:
<MajorInput
type="radio"
name="scale"
id="major"
label="major"
value="major"
checked={scale === 'major'}
onChange={handleChange}
onClick={() => setScale('major')}
/>
<MajorLabel
whileHover={{ scale: 1 }}
whileTap={{ scale: 0.9 }}
htmlFor="major"
>
Major
</MajorLabel>
...then to control the note buttons, like so:
<NoteInput
id="c"
scale={scale}
type="radio"
name="notes"
label="c"
value="c"
onClick={
() => setNotes(
scale === 'major' ? ['c1p', 'd1ma', 'e1ma', 'f1p', 'g1p', 'a1ma', 'b1ma'] :
scale === 'minor' ? ['c1p', 'd1ma', 'eb1mi', 'f1p', 'g1p', 'ab1mi', 'bb1mi'] :
''
)
}
/>
<NoteLabel
whileHover={{ scale: 1 }}
whileTap={{ scale: 0.9 }}
scale={scale}
htmlFor="c"
>
{
scale === 'major' ? 'C' :
scale === 'minor' ? 'C' :
'C'
}
</NoteLabel>
...and the state is established via two useState hooks, like so:
const [ scale, setScale] = useState('')
const [ notes, setNotes] = useState([])
...then ultimately the imported Keyboard component receives it's notes={notes} prop from the notes buttons, like so:
<Keyboard octaves={'2'} notes={notes}/>
...so i don't really know how I could make the note buttons be aware or know about the scale buttons being clicked and then translate that information to the keyboards notes prop, which is where i am stuck now...
Here is a code sandbox of the the component:
https://codesandbox.io/s/scales-981uk
Thank you for your help :-)
I looked through your code.
The problem you have right now is that your note components are not rerendered and restyled when the major button is clicked. In React, you can use the change in props to trigger a rerender which will also restyle your notes.
I would suggest to put all your note inputs into a sub component. :
// ./Scales.js
import NoteButtonBox from ./NoteButtonBox.js
//...
// important: pass scale as props.
// Everytime scale changes, the note buttons are rerendered and restyled
sub component
return (
<Container>
<ScaleButtonBox>
{/* ... */}
<ScaleButtonBox>
<NoteButtonBox scale={scale} />
{/* ... */}
// ./NoteButtonBox.js
const NoteButtonBox = ({scale}) => {
// Don't forget to also move 'const [notes, setNotes] = useState([]);' into the
const [notes, setNotes] = useState([]);
return {
/* return your styled notes based on props scale */
}
}
EDIT
For situations where you need to access state across multiple sub components, you can use React Context. Alternatively, you can use React Redux which accomplishes the same thing. I would suggest Comtext because it is a native part of React.
Breaking up your app into sub components might seem like unnecessary work but it is good practice to create many small components in React. React encourages composition like I suggest.
Create a NotesStateContext class.
You define the where it is needed NotesStateContext.Provider:
<NotesStateContext.Provider value="dark">
<NoteButtonBox />
</* All other components that need to access the state of your notes */>
</NotesStateContext.Provider>
Get and update your state from the provider

React global click track handler

I am new to react and working on a legacy codebase. Am wondering if we can write a global button click handler for click tracking.
The jQuery equivalent of this would be something like,
utilities.js :
var subscribeForClickTracking = function() {
$( "button" ).click((event) => { console.log($(event.target).html()) })
$( "p" ).click((event) => { console.log($(event.target).html()) })
}
In all the html files, will add reference to utiliies.js and this snippet of code.
$(document).ready(function () {
subscribeForClickTracking();
});
I have surfed about this and reached similar so questions, like
Higher Order React Component for Click Tracking
and https://www.freecodecamp.org/news/how-to-track-user-interactions-in-your-react-app-b82f0bc4c7ff/
But these solutions involve modifying the button's implementation, which would lead to huge change. (For a html form with 50+ buttons).
Is there an alternate approach to achieve something similar to the above jQuery approach.
Thanks in advance.
No, you can not do that. The reason is that React prevents you from doing global things to avoid side effects.
I think the proper React way would be to create your own Button component.
First create a new component :
export default Button = (props) => <button ...props />
Then, you can import and use Button instead of button in any component.
Then in your Button component, you can override your onClick method like this :
<button
...props
onClick={() => {
// doWhatYouWantHere;
props.onClick()
/>
However, as React is JavaScript, you can still use vanilla JavaScript to attach an event to every button

React pass an event into child component

I'm trying to find a way to pass event to child component in react.
I have something like that:
class ParentComponent extends Component<IProps, IState> {
render() {
return (
<div className={"ParentComponent"} onClick={this.onPageClick}>
... some navbar ...
... some `tabs` component
...
<ChildComponent/>
<AnotherChildComponent/>
...
... some footer ...
</div>
)
}
}
the child components are actually sub pages (changed using the tabs) with lot of logic and data inside them. (so I prefer manage them in separate components rather then one giant page).
some of the inner component have Editable labels which changed into an input (or in other case to a textarea or to MD editor) when the label is clicked.
there is an inner state in the child components when the user enter
into "Edit Mode" of the label. every component can have several of
this editable-labels.
The product request is when the user is clicking anywhere in the page the labels should exit from edit mode, so I need to capture the onClick on the master div like in the example and pass somehow the event into a function into the active child component so it will update it's inner state to exit edit-mode (if any).
Now, the solution I thought is to create a state variable in the parent which will be changed by the onPageClick function and pass into the child components
so they could update the local state. and then reset it on the parent again.
something like:
onPageClick() {
this.setState({ pageClicked: true }, () => {
this.setState({ pageClicked: false }
});
}
...
<ChildComponent pageClicked={this.state.pageClicked}/>
But it will change the parent state twice per click (and thus also the child state) even if not neccesary. the ideal why if I'll find a way to pass some event delegate to the children so a function will be triggered only inside the child when the parent onClick is triggered without any state changes in the parent.
Doe's it possible? do anyone have an idea how to implement something like that?
You are making your problem way more complicated than it needs to be.
You shouldn't listen for clicks on the outside component.
Instead, you should use your text input's onBlur event.
onBlur event is fired whenever a text input loses focus.

Mimic React custom component

I have a custom Reactjs component to display Pagination with next/previous buttons at the bottom of a grid. Now, the business needs to display the same component on top of the grid as well. How to display the previous /next button events based on the input provided in prev/next buttons at the bottom of the grid?
I tried using javascript innerHTML to mimic the behaviour. It works only with the display. It does not attach the event listener of the buttons. I tried even with
document.querySelector.addEventListener('click', ()=>{console.log('test')})
It does not work. Is there a better way to do with react.
I am going to just add some more content to Shmili Breuer answer.
If i understood you correctly you have 2 navigations, one at the top one at the bottom. The way you connect them would be through a state of you component, or a parent component if you are using functional component to render pagination stuff. So if you change the state it will reflect on both of your navigations. Also you can use only one function here, by passing a parameter, im gonna copy a code from before mentioned answer.
// first create a function
nextFunction = (condition) => {
if(condition){
this.setState(prevState=>({
page: prevState.page-1
}))
} else {
this.setState(prevState=>({
page: prevState.page+1
}))
}
}
// then use it in your button
<button onClick={() => this.nextFunction(some condition)}>Next</button>
Just put that component on top and bottom
<Grid>
<Pagination />
{...someOtherComponents}
<Pagination />
</Grid>
it's ok in react. Optimization that you want to do is overhead.
In react you would add an onClick attribute to an element you want to handle a click on.
Something like this
// first create a function
nextFunction = () => {
do next functionality....
}
// then use it in your button
<button onClick={() => this.nextFunction()}>Next</button>
This way you can have both top and bottom pagination call the same function.
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.

Resources