React - Call function from non-parent component with state from separate component - reactjs

I'm attempting to call a function from an AppBar with the state from a child component, like so
// App.js
<BrowserRouter>
<Nav />
<Routes>
<Route exact path={"/"} element={<MyComponent/>}/>
<Routes>
</BrowserRouter>
//Nav.js
function Nav() {
return (
<div>
<h1>Hello World</h1>
<button onClick={logChildState}>Get State</button>
</div>
)
}
// MyComponent.js
function MyComponent() {
const [someState, setSomeState] = useState({
Some state values....
})
return (
<div>
<input />
...more components...
</div>
)
}
logChildState() == "Some state values...."
The goal is to have the AppBar have a button with a function call that captures the state of MyComponent. As this is a simplified example, I will just say that the state should exist in the child, and it's not possible to hoist the state to App.js - because of this, I don't see a way to accomlish what I'm looking for easily, I've looked at possibly achieving this using context or an observable but it would be quite messy.
I'm wondering what the best way to tackle this kind of issue would be, or if my best choice would just be to have the "button" in Nav.js in the MyComponent.js.
Thanks

You can add the function as a prop like this:
//Nav.js
function Nav({logChildState}) {
return (
<div>
<h1>Hello World</h1>
<button onClick={() => logChildState('send this message')}>Get State</button>
</div>
)
}
And in your app Component you can simply take it as a prop like this:
<Nav logChildState = {logChildState}/>
and if you want to print the message comming from Nav component simply do this in App.js
const logChildState = (message) => {
console.log(message);
}
Hope that helps!

Related

React JSX to string

How can I convert the children of a React component to a string without it rendering?
import { renderToString } from 'react-dom/server';
const MyComponent = () => {
return (
<h1>Hello</h1>
)
}
const Output = () => {
return (
<pre>
{renderToString(
<div>
<MyComponent />
<MyComponent />
<MyComponent />
</div>
}
</pre>
)
}
How can I make the above show:
<div>
<MyComponent />
<MyComponent />
<MyComponent />
</div>
instead of
<div>
<h1>Hello</h1>
<h1>Hello</h1>
<h1>Hello</h1>
</div>
Ultimately I'm looking to show how the code for the component is written in a ui system. Trying to parse the children instead of writing the component twice. Any examples of open source websites demonstration ui components with a code block works too, and I'll just look into that.
Take a look at react-code-blocks. Basically you can add any code, just like Stackoverflow.

Change the state of another component using functionnal components in React

I have a button in my header that has to switch the state of the lateral menu of the page (to know if it has to be shown or not). I found out on Internet how to do so using Class Components, but not with Functional Components, do you have any idea on how to achieve that ?
Here's a simplification of my actual code in order to reproduce my issue (I removed all useless code).
App.js :
function App() {
return (
<div className="App">
<Header />
<div>
<LateralMenu />
</div>
</div>
);
}
The Header component :
function Header() {
const [lateralIsOpen, setLateralIsOpen] = useState(true);
function changeLateralMenu() {
setLateralIsOpen(!lateralIsOpen);
}
return (
<header>
<div onClick={ changeLateralMenu }>
</header>
);
}
And the LateralMenu component :
function Header() {
const [lateralIsOpen, setLateralIsOpen] = useState(true);
return (
<section>
{ lateralIsOpen ? "open" : "closed" }
</section>
);
}
I tried (but maybe not correctly) to declare the lateralIsOpen State in the App component and sending it through props to my children componenents (Header & LateralMenu).
I also tried looking at this question (and a few others) which is pretty similar, but don't see how I can apply it in my case because (as I understand) he uses a button in the parent component, that changes a state in the parent component, and then send it to the children through props... Where in my case, the button to switch it is already in a child.
I'd suggest you to move the state out of the LateralMenu to the parent (App) and then just pass the toggle function to the Header to toggle it.
export default function App() {
const [lateralIsOpen, setLateralIsOpen] = useState(true);
return (
<div className="App">
<Header toggleLateral={() => setLateralIsOpen((prev) => !prev)} />
<div>
<LateralMenu isOpen={lateralIsOpen} />
</div>
</div>
);
}
function Header({ toggleLateral }) {
function changeLateralMenu() {
toggleLateral();
}
return (
<header>
<div onClick={changeLateralMenu}>click</div>
</header>
);
}
function LateralMenu({ isOpen }) {
return <section>lateral is {isOpen ? 'open' : 'closed'}</section>;
}
Codesandbox: https://codesandbox.io/s/eager-heyrovsky-z75njd

Issue with rendering a component as a prop

I have a layout component which acts as follows:
const Layout = ({ children, sider }) => (
<div>
<div>{sider}</div>
{children}
</div>
)
But I can't implement it as expected. I'm hoping to get it implemented as follows:
<Layout sider={Sider}>
Body Content Here
</Layout>
Where Sider is another React component.
However when I do this, my Sider component doesn't render.
I can only get it to work like this:
<Layout sider={<Sider />}>
Body Content Here
</Layout>
How can I update my Layout component so that I can implement sider={Sider} instead of sider={<Sider />}?
Your 2nd approach is correct however if you still want to do that way, do like this
const Layout = ({ children, sider: Sider }) => (
<div>
<div>
<Sider />
</div>
{children}
</div>
)
Another method to handle this is to use React.createElement()
Example:
const Layout = ({ children, sider }) => (
<div>
<div>{React.createElement(sider)}</div>
{children}
</div>
)

How can I wait for init config to complete?

I've this code
class ConnectedApp extends Component {
constructor(props) {
super();
props.initConfig(); // the ajax call that populate the user settings
}
render() {
return (
<I18nextProvider i18n={i18n}>
<Router>
<div className="App" style={appStyle}>
<Head/>
<div className="Container">
<Container/>
</div>
<Foot/>
<Loading/>
<ToastContainer position="bottom-right" />
</div>
</Router>
</I18nextProvider>
);
}
}
Now the problem is that the initconfig is an ajax function in middleware. Before rendering the app for logged user I need to wait that the function has finished. Anyone have some suggestion?
Actually the app works nut on first login give an error and that error is resolved by manual refresh.
You'll need to change a few things here– Firstly you will need some way to indicate loading. This can be done in it's simplest form using a boolean either in your global or local state. You should also move your AJAX call method into the appropriate component lifecycle method componentDidMount.
You want it in your componentDidMount to ensure the component is mounted and ready to receive props or state changes.
class ConnectedApp extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.initConfig();
}
render() {
if (this.props.loading === true) {
return null // this will render nothing until loading is `false`
}
return (
<I18nextProvider i18n={i18n}>
<Router>
<div className="App" style={appStyle}>
<Head/>
<div className="Container">
<Container/>
</div>
<Foot/>
<Loading/>
<ToastContainer position="bottom-right" />
</div>
</Router>
</I18nextProvider>
);
}
}
In your redux state you would want to set a property for the loading state. When you start the request, you would set loading to true, when it is successful set it to false. If it fails, you would need a more expandable solution to account for that other than a simple 'loading' boolean.
Also, if you haven't checked out the new React Hooks API, this is what your component would look like using that.
import React, { useEffect } from "react";
const ConnectedApp = ({ initConfig, loading }) => {
useEffect(() => {
initConfig() // this will only get called when the component mounts. Same as `componentDidMount`
}, [])
if (loading === true) {
return null // this will render nothing until loading is `false`
}
return (
<I18nextProvider i18n={i18n}>
<Router>
<div className="App" style={appStyle}>
<Head/>
<div className="Container">
<Container/>
</div>
<Foot/>
<Loading/>
<ToastContainer position="bottom-right" />
</div>
</Router>
</I18nextProvider>
);
}

Adding a Link as a child of a Router with ReactDOM.render yields "You should not use <Link> outside a <Router>"

I am looking for a way to use ReactDOM.render to create a Link within a react router. The setup more or less looks like this:
const router = (
<div>
<Router>
<Route path="/map" component={Map}/>
</Router>
</div>
);
The relevant parts of Map.jsx look like this:
const MapPopup = () => {
return (
<Link to={`/map/add`} />
)
}
class Map extends React.Component {
componentDidMount() {
this.map = L.map('map')
//...stuff...
this.map.on('contextmenu', event => {
popup
.setLatLng(event.latlng)
.addTo(this.map)
.setContent(
ReactDOM.render(
MapPopup(),
document.querySelector('.leaflet-popup-content')
)[0]
)
.openOn(this.map)
})
}
render() {
return (
<React.Fragment>
<div id="map" />
</React.Fragment>
)
}
}
I am basically trying to add a Link to the map popup provided by leaflet (I can't use react-leaflet for this project). If I however return the MapPopup directly in the render function it works (obviously not in the popup but the Link does work this way).
<React.Fragment>
<div id="map" />
<MapPopup />
</React.Fragment>
Does anyone have an idea how I can tackle this rather unusual problem?
I am using "react-router-dom": "4.3.1".
This is the expected error since <Link> component expects ancestor component to be of router type (<BrowserRouter>, <MemoryRouter>, <Router> ... ), refer this thread for a more details.
For your scenario to circumvent this limitation ReactDOM.createPortal could be utilized instead of ReactDOM.render:
<Route
path="/popup"
render={() => (
<Popup>
<div>
Some content goes here
<Link to="/map"> Back to map</Link>
</div>
</Popup>
)}
/>
where
class Popup extends React.Component {
render() {
return ReactDOM.createPortal(
this.props.children,
document.querySelector("#link-render-div")
);
}
}
and
Here is a demo for your reference

Resources