Communicating between nested child components in React.js - javascript

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.

Related

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

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?

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

Which way is better for sending functions references to react components?

I'm trying to send function reference to imported component. So it's better I use ref attribute or props ?
I want to find an optimum and standard method.
<MyComponent ref="ListView" />
Or this method:
<MyComponent show="this.showModal" hide="this.hideModal" />
There wouldn't be an effect if you pass functions as a string (like "this.showModal").
Pass them down in curly braces:
<MyComponent show={this.showModal} hide={this.hideModal} />
Refs purpose is to manipulate DOM directly, not to pass something to the component. For example, you may want to manipulate DOM with jQuery
We don't use refs for event handling it is preferable to pass handlers as props to child component using JSX sytax with {} braces.
You want to send eventhandler to child component from parent if i understand your question correctly?
parent.js
class ParentComponent extends Component {
handler = () => {
console.log("handler click">
}
render() {
return (
<div>
<Child handler={this.handler} />
</div>
);
}
}
child.js
const Child = ({ handler }) => (
<Button onClick={handler} />
)
EDIT:
I understood your question, if you have a lot of method you can use ...props but also you can pass props as separated methods.
I think you're referring to the differences between controlled and uncontrolled components :
uncontrolled-
https://reactjs.org/docs/uncontrolled-components.html
controlled-
https://reactjs.org/docs/forms.html
The answer for that is depending on your component "logic", if you want to controll it by the DOM(using refs) or by the component itself.

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.

Storing methods needed in all components

I have a universal app I'm developing for learning purposes. I'm managing the state of my app with Redux, so all my data will be available there. But I want to create some methods that I'm going to use in all my components. The problem is: where should I store this methods?
Adding them to a parent component and passing the methods as props doesn't seem very useful, because this is one of the things that Redux tries to solve. And I'm pretty sure that Redux is not a place for storing methods.
I know I can create a class in a file somewhere, export it, add some methods to it, and when I want to use one method in a component I can call this file, create an instance of the class and call the needed method; but this doesn't look very react to me…
Is there a right way to create methods available for all components?
I've had some success sharing functions between components using an approach similar to the following. I'm not sure this approach will solve your specific use case with regards to cookies, however.
These functions can be stored anywhere and imported wherever required. They accept a component as their first argument, then return a function that operates on the component passed in.
Indicative, untested code follows.
// An event handler than can be shared between multiple components
const handleChange = component => event => component.setState({ value: event.target.value });
class ComponentOne extends PureComponent {
state = {};
render() {
return (
<div>
{this.state.value}
<input onChange={handleChange(this)} />
</div>
);
}
}
class ComponentTwo extends PureComponent {
state = {};
render() {
return (
<div>
{this.state.value}
<input onChange={handleChange(this)} />
</div>
);
}
}

Categories

Resources