How to communicate between React components which do not share a parent? - javascript

I am adding React to an already existing front end and am unsure how to communicate data between components.
I have a basic text Input component and a Span component, mounted separately. When the user types into the input, I want the text of the span to change to what is input.
Previously I would start a React project from scratch and so have the Input and Span share an App component as a parent. I'd use a prop function to lift the text state from the Input to the App and pass it down the value to the Span as a prop. But from scratch is not an option here.
I've considered:
Redux etc. As I'm introducing React piece by piece to this project and some team members have no React experience, I want to avoid using Redux or other state management libraries until very necessary, and it seems overkill for this simple case.
React Context API. This doesn't seem correct either, as my understanding was that context API should be kept for global data like "current authenticated user, theme, or preferred language" shared over many components, not just for sharing state between 2 components.
UseEffect hook. Using this hook to set the inner HTML of the Span component i.e
function Input() {
const inputProps = useInput("");
useEffect(() => {
document.getElementsByClassName('page-title')[0].innerHTML = inputProps.value;
})
return (
<div>
<h3>Name this page</h3>
<input
placeholder="Type here"
{...inputProps}
/>
</div>
);
}
Which sort of negates the whole point of using React for the Span?
I've gone with the UseEffect hook for now but haven't found any clear answers in the React docs or elsewhere online so any advice would be helpful.
Thanks.
Input.jsx
import React, { useState, useEffect } from 'react';
function useInput(defaultValue) {
const [value, setValue] = useState(defaultValue);
function onChange(e) {
setValue(e.target.value);
}
return {
value,
onChange
}
}
function Input() {
const inputProps = useInput("");
useEffect(() => {
document.getElementsByClassName('page-title')[0].innerHTML = inputProps.value;
})
return (
<div>
<h3>React asks what shall we name this product?</h3>
<input
placeholder="Type here"
{...inputProps}
/>
</div>
);
}
export default Input;
PageTitle.jsx
import React from 'react';
function PageTitle(props) {
var title = "Welcome!"
return (
<span>{props.title}</span>
)
}
;
export default PageTitle
Index.js
// Imports
const Main = () => (
<Input />
);
ReactDOM.render(
<Main />,
document.getElementById('react-app')
);
ReactDOM.render(
<PageTitle title="Welcome"/>,
document.getElementsByClassName('page-title')[0]
);

In React, data is supposed to flow in only one direction, from parent component to child component. Without getting into context/redux, this means keeping common state in a common ancestor of the components that need it and passing it down through props.
Your useEffect() idea isn't horrible as a kind of ad hoc solution, but I would not make PageTitle a react component, because setting the value imperatively from another component really breaks the react model.
I've used useEffect() to set things on elements that aren't in react, like the document title and body classes, as in the following code:
const siteVersion = /*value from somewhere else*/;
//...
useEffect(() => {
//put a class on body that identifies the site version
const $ = window.jQuery;
if(siteVersion && !$('body').hasClass(`site-version-${siteVersion}`)) {
$('body').addClass(`site-version-${siteVersion}`);
}
document.title = `Current Site: ${siteVersion}`;
}, [siteVersion]);
In your case, you can treat the span in a similar way, as something outside the scope of react.
Note that the second argument to useEffect() is a list of dependencies, so that useEffect() only runs whenever one or more changes.
Another side issue is that you need to guard against XSS (cross site scripting) attacks in code like this:
//setting innerHTML to an unencoded user value is dangerous
document.getElementsByClassName('page-title')[0].innerHTML = inputProps.value;
Edit:
If you want to be even more tidy and react-y, you could pass a function to your input component that sets the PageTitle:
const setPageTitle = (newTitle) => {
//TODO: fix XSS problem
document.getElementsByClassName('page-title')[0].innerHTML = newTitle;
};
ReactDOM.render(
<Main setPageTitle={setPageTitle} />,
document.getElementById('react-app')
);
//inside Main:
function Input({setPageTitle}) {
const inputProps = useInput("");
useEffect(() => {
setPageTitle(inputProps.value);
})
return (
<div>
<h3>React asks what shall we name this product?</h3>
<input
placeholder="Type here"
{...inputProps}
/>
</div>
);
}

You can create a HOC or use useContext hook instead

Related

ReactJS code structure and rendering component

I'm doing my first steps with ReactJS by trying to develop a small powerlifting application:
the user enters the weight and the plates to use are displayed on the page.
I split my code like this:
a "Plate" component:
// props :
// - color (Red, blue, yellow, green, white, silver)
// - weight(25 or 2.5, 20, 15, 10, 5, 1.25)
function Plate(props){
return <div className={`Plate ${props.color}Plate`}>
<p>{props.weight}</p>
</div>
}
export {Plate};
a "WeightInput" component:
import React, {Component} from "react"
import { Plate } from '../Plate/Plate';
import * as ReactDOM from 'react-dom/client'
class WeightInputForm extends Component{
calculation = (weight) => {
if(weight === 20) {
console.log("empty bar");
return;
}
const possibleWeights = [25, 20, 15, 10, 5, 2.5, 1.25];
const orderedPlateColor = ["Red", "Blue", "Yellow", "Green", "White", "LittleRed", "Silver"];
let plates = [];
let sideWeight = (weight-20-5)/2;
let plateNumber = 0;
possibleWeights.forEach((currentWeight, index)=>{
while(currentWeight <= sideWeight && sideWeight % currentWeight != sideWeight){
plates.push(<Plate key={plateNumber++} color={orderedPlateColor[index]} weight={currentWeight}></Plate>);
sideWeight -= currentWeight;
}
});
return plates;
}
handleSubmit = (event) =>{
event.preventDefault();
const weightSubmitted = event.target[0].value;
if(weightSubmitted === "") return;
const platesRoot = ReactDOM.createRoot(document.getElementsByClassName("Plates")[0]);
const plates = this.calculation(weightSubmitted);
platesRoot.render(plates);
}
render() {
return (<form onSubmit={this.handleSubmit}>
<input type="number" step="2.5" min={20}/>
<input type="submit" value="Submit"/>
</form>);
}
}
export default WeightInputForm;
here is the app file :
import './App.css';
import WeightInputForm from './components/WeightInput/WeightInput';
function App() {
return (
<div className="App">
<h1>How to load your powerlifting bar ?</h1>
<div className="Plates">
</div>
<div className='InputWeight'>
<WeightInputForm></WeightInputForm>
</div>
</div>
);
}
export default App;
Currently my application works as I want, however I have this warning in the console:
You are calling ReactDOMClient.createRoot() on a container that has already been passed to createRoot() before. Instead, call root.render() on the existing root instead if you want to update it.
I also tried not to use createRoot but only ReactDOM.render, obviously it is deprecated since React 17.
I have the impression that the way I qplir my application is not the right one.
Would you have any advice to remove this warning and make my code cleaner?
Thanks in advance.
You're kind of misunderstanding many things. With React the goal should be to rid of every direct contact with de DOM. document.get... should not be used.
Instead use react hooks such as useState to dynamically display HTML contain. Make use of JSX syntax. Read more on React before getting started.
In most react app render is done once and only in the index.js (root file).
There's quite a lot going on here which reflects a slightly flawed - but salvageable - understanding of how React works. Probably the key issue is how to utilise state to ensure that components properly update/reflect state changes, but I shall start at the beginning.
A minor issue is that you're mixing function components and class components. I would stick with one or the other, particularly if you're writing all the code yourself.
You should import Plate into App because that's presumably where it's going to be used. You should export it as export default Plate; just like the other components, and then import Plate from './Plate/plate';.
For this app you should be using state management. There are lots of ways to approach this but the useState hook would be the easier here. Since the output of Plate relies on the value of Input and both of those components are in App the state should be held in App.
To manage the state: create a function/handler in App that updates the state using the value from Input. Pass down that handler to the Input component, and that can call it whenever the input value changes.
As you can see in the abbreviated example (which just shows the main concepts) Plate accepts the weight state as as property. When the state changes those changes are passed down to `Plate so it can render that value.
In summary it maybe a good idea for you to visit/revisit how React actually works, and take a look at some tutorials. Understanding how React components and state work together is rather pivotal to development with the library, and it's an important step to grasp before you can build an application properly.
Here's the example.
App contains both the Plate and Input components. It has state (weight) and a way to update that state (setWeight). It also has a handler (handleChange). It passes that handler down to Input which calls it when the change event fires on the input element. When that handler is called it updates the state with the input's value.
The weight state is passed down to the Plate component. When that state changes those changes are reflected in the re-rendered component.
const { useState } = React;
// Plate accepts a weight state value
// and renders it
function Plate({ weight }) {
return <div>Plate: {weight}</div>;
}
// Input accepts the weight state value (the component
// is what is known as a controlled component), and the
// change handler. When the input element fires an event
// it calls that handler
function Input({ weight, handleChange }) {
return (
<input
type="number"
step="2.5"
min="20"
value={weight}
onChange={handleChange}
/>
);
}
function Example() {
// Initialises state with `useState`
const [ weight, setWeight ] = useState(0);
// The handler - it accepts the event from the
// element that fired it, and then grabs the value
// from the element, and then updates the state with it
function handleChange(e) {
setWeight(e.target.value);
}
// The main component contains both the Plate
// component, and the Input component, passing down
// the relevant state, and handler to each
return (
<main>
<Plate weight={weight} />
<Input
weight={weight}
handleChange={handleChange}
/>
</main>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

What exactly does useContext do behind the scenes? [duplicate]

This question already has an answer here:
How to solve problem with too many re-renders in context?
(1 answer)
Closed 10 months ago.
Hi everyone I'm new to React. I was trying to create a global state using the context api. I encountered something unexpected. What I've learned is that when a context is created, it has a provider that will wrap around those components that need the data and when the value changes, the provider will rerender all the wrapped components, right?
Take a look at this:
// AuthContext.js
export const AuthContext = createContext(null);
const AuthProvider = ({ children }) => {
const [user, setUser] = useState({
name: null,
});
return (
<AuthContext.Provider value={{ user, setUser }}>
{children}
</AuthContext.Provider>
);
};
// index.js
<AuthProvider>
<App />
</AuthProvider>
// App.js
function App() {
console.log("[App] ran");
return (
<>
<Dashboard />
<Login />
</>
);
}
// Dashboard.js
function Dashboard() {
console.log("[Dashboard] ran");
return <div>Dashboard</div>;
}
// Login.js
function Login() {
console.log("[Login] ran");
const { user, setUser } = useContext(AuthContext);
const inputNameRef = useRef();
return (
<div>
<input placeholder="Enter your name..." ref={inputNameRef} />
<button
onClick={() => {
setUser(inputNameRef.current.value);
}}
>
Submit
</button>
</div>
);
}
When the code runs for the first time the output is:
[App] ran
[Dashboard] ran
[Login] ran
and when the submit button is clicked, the setUser function will be called and a new value will be set to the AuthProvider state. The provider should rerender all of the components, and the above output should again be logged but nope, the output is just:
[Login] ran
Something that is interesting to me is that when I use useContext in the Dashboard component it works, I mean the component will be rerendered. It's related to the useContext hook I think but don't know how it works. What is going on under the hood?
To quote React's official documentation on Context (https://reactjs.org/docs/context.html):
All consumers that are descendants of a Provider will re-render whenever the Provider’s value prop changes. The propagation from Provider to its descendant consumers (including .contextType and useContext) is not subject to the shouldComponentUpdate method, so the consumer is updated even when an ancestor component skips an update.
You can think of "consumer" as the component whose state "consumes" the useContext hook. AKA you'll only see a re-render from the components where the hook is used. This way not ALL children inside a Context Provider will be re-rendered on a state change, only those who consume it.

Debounced change handler in React won't change input if value is defined

I'm coding a simple Reddit client in JavaScript using React, and I have an element in my header that changes the subreddit to be displayed - with the Lodash debounce applied inside of the React useMemo hook to prevent excessive handleChange calls while typing.
However, when the value prop of the input element is set to the appropriate useState variable, the input becomes impossible to alter.
This is the code that works, albeit with limitations I'm hoping to overcome:
import React, { useMemo } from 'react';
import debounce from 'lodash.debounce';
export const Header = ({ subreddit, setSubreddit }) => {
const handleChange = ({ target }) => {
setSubreddit(target.value)
}
const debouncedHandleChange = useMemo(() => {
return debounce(handleChange, 500)
}, [])
return (
Superliminal/ <input type='text' onChange={debouncedHandleChange} placeholder={subreddit} autofocus/>
)
}
Things get problematic when you define the value prop of the input element like so:
value={subreddit}
Currently, I don't understand why this causes the input to become inalterable, and I would prefer it to be the case that the value, and not just the placeholder, remains equal to the subreddit state variable. I especially don't understand because without the debounce implementation, you can define value as the state variable without issues.
If it's necessary, the code in the App.js file for setting the subreddit is simply:
import React, { useState } from 'react'
import { Header } from './components/Header/'
function App() {
const [ subreddit, setSubreddit ] = useState('all')
return (
<>
<Header subreddit={subreddit} setSubreddit={setSubreddit} />
</>
)
}
Could someone please explain to me why my code isn't working as intended and help me fix it?
Thanks in advance!!

Unable to use data from chlid component to parent component?

I've made this react app in which there's a Parent component as App.js and it has three child components.The first child component has some states which are being changed by the other two child components present in parent component.
So first I'm passing the states in parent component and then passing them into other two child components as props. Here's the piece of my code.
Here's my App.js
function App() {
var firstvalueoftextarea,
setfirstvalueoftextarea,
secondvalueoftextarea,
setsecondvalueoftextarea;
var PanelOfResult = function(
firstvalueoftextarea,
setfirstvalueoftextarea,
secondvalueoftextarea,
setsecondvalueoftextarea
) {
firstvalueoftextarea = firstvalueoftextarea;
setfirstvalueoftextarea = setfirstvalueoftextarea;
secondvalueoftextarea = secondvalueoftextarea;
setsecondvalueoftextarea = setsecondvalueoftextarea;
};
return (
console.log("Rendering of component"),
(
<div className="App">
<ResultPanel PanelOfResult={PanelOfResult} />
<SecondChildComponent
valueoftextarea={firstvalueoftextarea}
setvalueoftextarea={setfirstvalueoftextarea}
/>
<ThirdChildComponent
className="Symbols"
firstvalueoftextarea={firstvalueoftextarea}
setfirstvalueoftextarea={setfirstvalueoftextarea}
secondvalueoftextarea={secondvalueoftextarea}
setsecondvalueoftextarea={setsecondvalueoftextarea}
/>
</div>
)
);
}
export default App;
Here's my ResultPanel.js
import React from "react";
function ResultPanel(props) {
const [firstvalueoftextarea, setfirstvalueoftextarea] = React.useState(
"Hello World"
);
const [secondvalueoftextarea, setsecondvalueoftextarea] = React.useState("");
props.PanelOfResult(
firstvalueoftextarea,
setfirstvalueoftextarea,
secondvalueoftextarea,
setsecondvalueoftextarea
);
return (
<div>
<h1>{firstvalueoftextarea}</h1>
</div>
);
}
export default ResultPanel;
The states of the ResultPanel Component can be changed by SecondChildComponent and ThirdChildComponent and those component code is working fine.
When SecondChildComponent tries to change the state of ResultPanel I'm getting the error- TypeError: this.props.setvalueoftextarea is not a function
Can anyone help where I'm mistaking? I'm not certain where to call PanelOfResult callback method in ResultPanel component.
There's a lot of issues here, one of the biggest being how you set your variables for state.
First, move your state declarations to the parent component, and delete your vars. The whole PanelOfResult is unnecessary and a bad pattern of doing things.
function App() {
const [firstvalueoftextarea, setfirstvalueoftextarea] = React.useState(
"Hello World"
);
const [secondvalueoftextarea, setsecondvalueoftextarea] = React.useState("");
Then, instead of passing the callback, pass the needed values:
<ResultPanel firstvalueoftextarea={firstvalueoftextarea} />
This will stop your error about not being a function, and eliminate the need for a callback.
In general you should try not to use var, but let or const.
Also, try naming variables better. Either camel case or underscore would make your code much more readable.

Understanding React Higher-Order Components

Can someone please explain Higher-order components in React. I have read and re-read the documentation but cannot seem to get a better understanding. According to the documentation, HOCs help remove duplication by creating a primary function that returns a react component, by passing arguments to that function.
I have a few questions on that.
If HOCs create a new enhanced component, can it be possible not to pass in any component as argument at all?
In an example such as this, which is the higher order component, the Button or the EnhancedButton.
I tried creating one HOC like this:
// createSetup.js
import React from 'react';
export default function createSetup(options) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.testFunction = this.testFunction.bind(this);
}
testFunction() {
console.log("This is a test function");
}
render() {
return <p>{options.name}</p>
}
}
}
// main.js
import React from 'react';
import {render} from 'react-dom';
import createSetup from './createSetup';
render((<div>{() => createSetup({name: 'name'})}</div>),
document.getElementById('root'););
Running this does not show the HOC, only the div
Can anyone help out with a better example than the ones given?
A HOC is a function that takes a Component as one of its parameters and enhances that component in some way.
If HOCs create a new enhanced component, can it be possible not to pass in any component as argument at all?
Nope, then it wouldn't be a HOC, because one of the conditions is that they take a component as one of the arguments and they return a new Component that has some added functionality.
In an example such as this, which is the higher order component, the Button or the EnhancedButton.
EnhanceButton is the HOC and FinalButton is the enhanced component.
I tried creating one HOC like this: ... Running this does not show the HOC, only the div
That's because your createSetup function is not a HOC... It's a function that returns a component, yes, but it does not take a component as an argument in order to enhance it.
Let's see an example of a basic HOC:
const renderWhen = (condition, Component) =>
props => condition(props)
? <Component {...props} />
: null
);
And you could use it like this:
const EnhancedLink = renderWhen(({invisible}) => !invisible, 'a');
Now your EnhancedLink will be like a a component but if you pass the property invisible set to true it won't render... So we have enhanced the default behaviour of the a component and you could do that with any other component.
In many cases HOC functions are curried and the Component arg goes last... Like this:
const renderWhen = condition => Component =>
props => condition(props)
? <Component {...props} />
: null
);
Like the connect function of react-redux... That makes composition easier. Have a look at recompose.
In short, If you assume functions are analogues to Components, Closure is analogous to HOC.
Try your createSetup.js with:
const createSetup = options => <p>{options.name}</p>;
and your main.js
const comp = createSetup({ name: 'name' });
render((<div>{comp}</div>),
document.getElementById('root'));
A higher-order component (HOC) is an advanced technique in React for reusing component logic. Concretely, a higher-order component is a function that takes a component and returns a new component.
A HOC is a pure function with zero side-effects.
Example: CONDITIONALLY RENDER COMPONENTS
Suppose we have a component that needs to be rendered only when a user is authenticated — it is a protected component. We can create a HOC named WithAuth() to wrap that protected component, and then do a check in the HOC that will render only that particular component if the user has been authenticated.
A basic withAuth() HOC, according to the example above, can be written as follows:
// withAuth.js
import React from "react";
export function withAuth(Component) {
return class AuthenticatedComponent extends React.Component {
isAuthenticated() {
return this.props.isAuthenticated;
}
/**
* Render
*/
render() {
const loginErrorMessage = (
<div>
Please login in order to view this part of the application.
</div>
);
return (
<div>
{ this.isAuthenticated === true ? <Component {...this.props} /> : loginErrorMessage }
</div>
);
}
};
}
export default withAuth;
The code above is a HOC named withAuth. It basically takes a component and returns a new component, named AuthenticatedComponent, that checks whether the user is authenticated. If the user is not authenticated, it returns the loginErrorMessage component; if the user is authenticated, it returns the wrapped component.
Note: this.props.isAuthenticated has to be set from your application’s
logic. (Or else use react-redux to retrieve it from the global state.)
To make use of our HOC in a protected component, we’d use it like so:
// MyProtectedComponent.js
import React from "react";
import {withAuth} from "./withAuth.js";
export class MyProectedComponent extends React.Component {
/**
* Render
*/
render() {
return (
<div>
This is only viewable by authenticated users.
</div>
);
}
}
// Now wrap MyPrivateComponent with the requireAuthentication function
export default withAuth(MyPrivateComponent);
Here, we create a component that is viewable only by users who are authenticated. We wrap that component in our withAuth HOC to protect the component from users who are not authenticated.
Source
// HIGHER ORDER COMPOENTS IN REACT
// Higher order components are JavaScript functions used for adding
// additional functionalities to the existing component.
// file 1: hoc.js (will write our higher order component logic) -- code start -->
const messageCheckHOC = (OriginalComponent) => {
// OriginalComponent is component passed to HOC
const NewComponent = (props) => {
// business logic of HOC
if (!props.isAllowedToView) {
return <b> Not Allowed To View The MSG </b>;
}
// here we can pass the props to component
return <OriginalComponent {...props} />;
};
// returning new Component with updated Props and UI
return NewComponent;
};
export default messageCheckHOC;
// file 1: hoc.js -- code end -->
// file 2: message.js -- code start -->
// this is the basic component we are wrapping with HOC
// to check the permission isAllowedToView msg if not display fallback UI
import messageCheckHOC from "./hoc";
const MSG = ({ name, msg }) => {
return (
<h3>
{name} - {msg}
</h3>
);
};
export default messageCheckHOC(MSG);
// file 2: message.js -- code end -->
// file 3 : App.js -- code start --->
import MSG from "./message.js";
export default function App() {
return (
<div className="App">
<h3>HOC COMPONENTS </h3>
<MSG name="Mac" msg="Heyy !!! " isAllowedToView={true} />
<MSG name="Robin" msg="Hello ! " isAllowedToView={true} />
<MSG name="Eyann" msg="How are you" isAllowedToView={false} />
</div>
);
}
// file 3 : App.js -- code end --->

Categories

Resources