Registering (and Unregistering) Multiple Hotkeys on Same Page with useMousetrap Hook? - javascript

I have a component that uses react hooks to register hotkeys: useMousetrap.
That's a hooks-wrapper for the mousetrap library.
Here is the component:
const MyComponent = ({ name }) => {
const sayHi = () => {
alert(`Hello from Component ${name}`);
};
useMousetrap('x', sayHi);
return (
<div>
Component {name}
</div>
);
};
When I implement that component inside some other component or page, everything works fine. But now say I want to have two components on the same page:
<div>
<MyComponent name="A" />
<MyComponent name="B" />
</div>
Now both components try to register the hotkey. It seems like it's always the one that appears later in the document flow, that succeeds. So if I press x in the example above, the alert would say Hello from Component B.
Is there a way to make sure that only one component is ever active? I was thinking of registering and unregistering those hotkeys depending on which component is in view, but useMousetrap being a hook seems to make conditional logic quite difficult. I am bit confused here.
Is there a quick & easy way I can turn the hotkeys on/off conditionally?

Related

How to pass a renderer function with parameters to a component in React?

I want to create a customizable avatar upload component like this:
<AvatarUpload
...
renderSaveButton=({disabled}) => <Button disabled={disabled}>...</Button>
/>
As you can see the renderSaveButton function defines how the button should be rendered. More importantly, it also receives a parameter from the internal state of the AvatarUpload component.
Then in the AvatarUpload component itself, I render the button this way:
{renderSaveButton({disabled: value)}
The problem is that the linter is complaining that React is going to create a new function on every render, and I should not do it (the message is "do not define components during render").
Shall I care about this warning, and if so what's the alternative?
I searched on google but it's not clear to me what's the alternative to this render props technique.
If there's no measurable performance hit using an inline anonymous callback function then I don't see any issue with this. It's just a warning.
If there is a performance issue or you just really want to clear out all warnings then you can memoize the callback that is passed to children components using the useCallback hook.
Example:
const renderSaveButton = useCallback(({ disabled }) => (
<Button disabled={disabled}>
...
</Button>
), [/* place any dependencies here */]);
...
<AvatarUpload ... renderSaveButton={renderSaveButton} />

render with different child components from props

I am currently working in a scenario, where I need to be able to import a component from library, but tell it to choose different components for some of its child components to render with. In this case, it needs to choose different button components, for example. Now I already got this working, as in, it does what it needs to do, but I am wondering if there is maybe a more fitting/appropriate way of doing it.
export const container = ({component, children}) => {
const ButtonComponent = component?.button ?? Button;
return (
<div>
<ButtonComponent size="large">Do something</ButtonComponent>
</div>
)
}
In this case, Buttons are defined in this same library, but on the side of the application where the library is consumed, the buttons are modified, variants are added, some properties are added that are not part of the original component of the library. And I am telling the component to use a different component like this:
<container component={FancyButton} />
As I said this works, but it feels like there might maybe be a more elegant solution to this. This is relevant, because the library uses an atomic design methodology approach, where some of the more complex components use less complex components, but they are being modified for a specific usecase. So all the buttons are being modified to have additional variants, etc. But if I then go a head and for example use the modal, it uses the regular buttons, not the modified buttons. This is the solution that I came up with, that allows me to tell the component to use these modified buttons instead.
Does this make sense? Is this an anti-pattern? Is there a more efficient/elegant solution to this?
€1: Here's a codesandbox demonstrating what this does: https://codesandbox.io/s/practical-ives-jxk3v
const defaultComponents = {
button: BaseButton
}
export const Card = ({ components=defaultComponents, children }) => {
return (
<div className="card">
<h2>Thing</h2>
{children}
<components.button className="card__button">Click me</components.button>
</div>
);
};
I don't know is this help. I will do like this.
use = when you passing props in a function mean the default value of that prop.

Best way to get values from multiple complex child components?

I have a parent component that has base data called script, which has multiple sequences and each sequence is composed of multiple items (inputs, dropdown, ... ).
Now I need the updated data in parent since I want to put a save button that is going to save all forms with one click.
It looks something like this:
I tried two ways of handling this:
That each child had an onChange property
in which parent sets the state with the new data. But the problem
here is, that since this is quite a complex form, it re-renders
everything each time, so there was a noticeable delay when typing in
inputs.
The "bad" of just changing the props object in a child,
which is fast, but I know it is a bad practice.
What is the best way of handling forms on a scale like this? Should it be set up differently?
This is a question I've spent some time struggling with myself. There are multiple ways to maintain child state at a higher level; however, I've found that in your particular situation it is often best to use Redux.
To be clear, I generally avoid Redux at all costs (in favor of React's context), but Redux gives you the ability to subscribe to a particular piece of state in your child components. Listening to one piece of state in a child component will prevent your parent and sibling components from updating when you only need a single child to update. This ends up being far more efficient when handling multiple forms at one time.
For example, the following component will only listen to state updates that affect its own state. These updates will bypass the forms parent and sibling components:
import React from 'react';
import { connect } from 'react-redux';
import * as actions from 'redux/actions';
// Custom component
import { InputField } from 'shared';
const FormOne = ({ me, actions }) => (
<form>
<InputField
inputId="f1f1"
label="field one"
value={me.fieldOne}
onChange={(e) => actions.setFormOneFieldOne(e.target.value)}
/>
<InputField
inputId="f1f2"
label="field two"
value={me.fieldTwo}
onChange={(e) => actions.setFormOneFieldTwo(e.target.value)}
/>
<InputField
inputId="f1f3"
label="field three"
value={me.fieldThree}
onChange={(e) => actions.setFormOneFieldThree(e.target.value)}
/>
</form>
);
export default connect(state => ({ me: state.formOne }), actions)(FormOne);
In the above example FormOne is only listening for its own state updates; whereas, similar logic utilizing context instead of Redux will cause the entire component tree that the context provider is wrapping to update (including parent and sibling components):
import React, { useContext } from 'react';
// Custom component
import { InputField } from 'shared';
// Custom context - below component must be wrapped with the provider
import { FormContext } from 'context';
const FormTwo = () => {
const context = useContext(FormContext);
return(
<form>
<InputField
inputId="f2f1"
label="field one"
value={context.state.formTwo.fieldOne}
onChange={(e) => context.setFormTwoFieldOne(e.target.value)}
/>
<InputField
inputId="f2f2"
label="field two"
value={context.state.formTwo.fieldTwo}
onChange={(e) => context.setFormTwoFieldTwo(e.target.value)}
/>
<InputField
inputId="f2f3"
label="field three"
value={context.state.formTwo.fieldThree}
onChange={(e) => context.setFormTwoFieldThree(e.target.value)}
/>
</form>
);
};
export default FormTwo;
There are some improvements that can be made to both of the above components, but they are meant to serve as an example for how to connect child components to an elevated state. It is also possible to connect to a single parent component using props, but that is the least efficient option possible, and will clutter up your architecture.
Key takeaway: Use Redux for your use case. It's the most efficient option if it is implemented correctly.
Good luck!
Wrap all the forms in a component that will only deal with saving all the forms data and running the "save all" function:
the wrapper component should have a state the includes all the forms data, it should probably look something like this:
class Wrapper Component extends React.Component {
constructor(props) {
super(props);
this.state = {
formsData: {},
};
}
}
formsData should probably be structured pretty much like that:
{ 0: { title:"text", type:"video", etc:"etc" },
1: { title:"text", type:"video", etc:"etc" }}
the keys (0,1, etc..) represents the form id, and can be set to any unique modifier each for has.
then make the wrapper component handle the onChange for every individual form -> every change on each individual form should uplift the new state (new updated data) and update the formsData state obj accordingly:
const onChange(formData) {
const formattedData = {[formData.id]: {...formData}}
this.setState({formsData: {...formsData, ...formattedData}})
}
* This is just an example of a case where in each change in each form you uplift the entire data object, you can do it in many ways
Than, the save all button should also be handled in the wrapper component, and uplift all the data you stored with it to the relevant function in a parent component / handle it itself.
Good luck!
Lifting state up is indeed the correct way of doing this. To optimize child sections you can use
PureComponent ==> https://reactjs.org/docs/react-api.html#reactpurecomponent
AKA Memoized Component ==> https://reactjs.org/docs/react-api.html#reactmemo
React.memo is a higher order component. It’s similar to React.PureComponent but for function components instead of classes.
Also if you are within the hooks universe checkout
useCallback : https://reactjs.org/docs/hooks-reference.html#usecallback
useMemo : https://reactjs.org/docs/hooks-reference.html#usememo
If you are using Redux by any chance remember to look at
reselect : https://github.com/reduxjs/reselect

React context props drilling, what is it that I don't get?

There is a TL;DR at the bottom.
I am probably doing this wrong or using the context in an bad way. I am new to react so I have no clue if this is how we are meant to do things.
My understanding:
Context can be used to pass down props to deeper nested child components without having to pass them through all levels of nesting. A provider is filled with props, and a consumer will look "up the tree" to find the nearest provider and get it's data.
If this is the case, then I can load a provider with a function, such as an onChange handler in order to avoid having to write the handler on every child component when they all do the same thing. This would allow for a "smart form" which govern's its input's handlers by "passing" handlers given to it. Obviously just writing one handler on multiple components is not an issue, but having like 20-30 form fields and writing 4+ handlers on each of them just creates code clutter. So I tried the following:
HTML structure is like this, for example:
<ControlledForm data={some_data} handlers={some_handlers}>
<LabeledControl name="Type your name" rel="Name" meta={{some_meta_object}}></LabeledControl>
<LabeledControl name="Pet name" rel="PetName" meta={{some_meta_object}}></LabeledControl>
<LabeledControl name="Type of pet" rel="PetType" meta={{some_meta_object}}></LabeledControl>
<LabeledControl name="Family" rel="Family" meta={{some_meta_object}}></LabeledControl>
</ControlledForm>
And this is the ControlledForm class code:
const { Provider } = React.createContext(); //Note this
class ControlledForm extends Component {
state = {};
render() {
return (
<Provider value={{ onChange: this.props.onChange }}>
<form>{this.props.children}</form>
</Provider>
);
}
}
Now whatever child I place within this form would want to have a <Consumer> wrapper around it to consume the changeHandler, or at least this is the plan. However when I wrap my LabeledControl in a consumer, it acts as if it has no data.
<LabeledControl> (reduced code):
const { Consumer } = React.createContext();
class LabeledControl extends Component {
state = {};
render() {
return (
<Consumer>
{r => {
console.log("consumer:", r); //Logs undefined
return (
<div className="labeled-control">
{/*Code here*/}
</div>
);
}}
</Consumer>
);
}
}
If I was to guess at what the issue is, I'd say it is because both the ControlledForm and the LabeledControl create it's own context, which is not shared, look at the code above. But I do not understand how would I share this context and still keep the two classes in separate .js files. I cannot pass a reference down to the children, all I get is the {this.props.children} and no way to tell it "Hey use this provider here". All the examples I find online have the two classes that are a provider and a consumer in a same file, being able to reference the same "context" but this seriously impacts the freedom of what I can put inside a form, or rather doesn't let me have customization in terms of "children".
TLDR
How do I pass down a "Context" from a Provider to a Consumer when they are in two different javascript files? Code is above. I essentially need to pass down a handler to every child and have it (maybe, maybe not, depending on a child) use the handler to tell the parent to update it's data. All of this whilst using {this.props.children} in a parent in order to allow "outter code" to "inject" the parent component with any children desired and have them either use or not use the parent's handler.
Edit:
I searched about a bit and found two possible solutions, which I both tested and both seem to be working (with a limited use case). Both render props and React.CloneElement seem to do the trick when there is one level of nesting as we can directly render and add props to children with them, but when we need to prop drill several levels, all the components in between would have to implement the same passing of props which then turns to spaghetti code. Still searching to try and find the way to pass the context down to the children for consumption in different files.
Please view the code below.
Also: here is a sample project I have built:https://codesandbox.io/s/5z62q8qnox
import React from 'react'
import PropTypes from 'prop-types';
import 'bootstrap/dist/css/bootstrap.min.css';
export default class ControllerForm extends React.Component {
static childContextTypes = {
onChange: PropTypes.func.isRequired
}
getChildContext() {
return {
onChange: this.handleOnChange
}
}
handleOnChange = (e) => {
console.log(e.target.value) //here is the place you have to implement
}
render() {
return (
<div class="container">
{this.props.children}
</div>
)
}
}
import React from 'react'
import PropTypes from 'prop-types';
import 'bootstrap/dist/css/bootstrap.min.css';
export default class LabeledControl extends React.Component {
static contextTypes ={
onChange : PropTypes.func.isRequired
}
render() {
return (
<div>
<div className="form-group">
<input className="form-control" type="text" onChange={this.context.onChange} />
</div>
</div>
)
}
}
function App() {
return (
<div className="App">
<ControllerForm>
<LabeledControl />
<LabeledControl />
</ControllerForm>
</div>
);
}
It appears that Context is not what I should be using for this, instead either render props or React.cloneElement() is the proper solution, despite my best efforts to enforce a context.
Parent's render:
{this.props.children.map((child, index) =>
React.cloneElement(child, { key: index, handler: handler })
)}
Child's render:
return (
<div>
<span onClick={this.props.handler}>{passed.foo}</span>
</div>
);
This way the structure remains clean and handlers get passed down. Only issue is every component that needs to pass them down has to implement this, but it would have been the same with context, since it is not exported to a separate file.

Communicating between nested child components in React.js

It won't take you long to realise that I am probably out of my depth here. I am not only new to React.js but also to ES6 and so please be gentle with your answers...
Here goes.
I am using a component to build a form input:
const Input = (props) => {
return(
<input ... />
)
}
I have a component which I use to construct HTML around any of the basic form elements that I give it.
const InputWrap = (props) => {
return(
<div class="input-wrap" ...>
{children}
</div>
)
}
Which allows me to do something like this...
const Input = (props) => {
return(
<InputWrap>
<input ... />
</InputWrap>
)
}
What I would like to do is to add a character counting component to the mix:
const InputWrap = (props) => {
return(
<div class="input-wrap" ... >
{children} // which is the HTML input
{props.maxValue && <CharCounter />}
</div>
)
}
So here is my problem...
The <CharCounter /> needs to be notified of any changes happening to the <input /> and update it's internal state.
The <input /> and the <CharCounter /> are siblings and children of the <InputWrap /> and so, from what I can gather, I need a method inside <InputWrap /> which ties an onChange of the <input /> and some method that will update the state within the <CharCount /> component.
I am at a loss as to how I go about adding a callback as the <input onChange={doSomething} /> is in the form {children} by the time it comes in contact with the <CharCount /> once inside the <InputWrap />...
Where am I going wrong here? I'm starting to think it was way back at the beginning...
There are two typical ways of communication between siblings:
You use the InputWrapper as an DataContainer
You use a Data Flow library like flux or redux (which is a lot more complex, especially for this case)
For the 1. you need, as you correctly noticed, an onChange handler for the input component, which is a function defined in the Component and which is passed to the input. If your input component is an own component and not the native component you will need to pass the onChange prop to the native input.
The function in the Component takes the input, counts the chars and sets an internal state variable with setState({ charCount: #CountValue#}). And then you can pass the state variable to the CharCount Component with
One Important thing to mention: You are using stateless components and therefore you will need to change your InputWrap to a normal react component
class InputWrap extends React.Component {
...
}
Hope this will give you the right direction.
Another excellent solution that is like redux but has a different architecture and api is https://github.com/mobxjs/mobx-react.
You can use the inject HOC to inject shared state to any react component in your application.

Categories

Resources