Is it wrong export React Hooks to manage global state? - reactjs

I am exporting the return of a Hook which I use in the root component of a project. Then it becomes very easy for the other components to import globalState and setGlobalState ().
I did several tests here and it worked very well. The problem is that I have not seen anyone in the community using it in the same way.
import React, { useState } from "react";
import Level2 from "./components/Level2";
export let setGlobalState = () => {};
export let globalState = {};
const initalState = { counter: 0 };
const App = () => {
[globalState, setGlobalState] = useState(initalState);
return (
<>
<Level2 />
</>
);
};
export default App;
Is it wrong to manage global state in that way? If it is, why?
Here I have a repository with the whole project:
https://github.com/andregardi/global-state-with-hooks

This approach has some issues with updates. Only children of the App component can react to global state changes. Those children still might not get rerendered if something in the tree blocks an update (PureComponent, React.memo, etc.)
Also setGlobalState might get redefined by some module.
Check this example to observe the issue. All components will update the global state but the "broken" one will not react to updates because its props don't change.

It is not very correct to define states globally then mutating them because multiple instances of same component might need to have their own state and not share it. If you define the state globally all of them will share the same state and it will lead to inconsistencies
DEMO
const { useState } = React;
let initialState = 0;
let globalState;
let setGlobalState;
function Counter () {
[globalState, setGlobalState] = useState(initialState);
return (
<div>
<div>Count: {globalState}</div>
<button onClick={() => setGlobalState(count => count + 1)}>Increment</button>
</div>
)
}
function App() {
return (
<div>
<div>
<div>Counter 1: </div>
<Counter />
</div>
<div>
<div>Counter 2: </div>
<Counter />
</div>
</div>
)
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>

Related

Pass state onto {children} React hook

So I have a component that looks like this:
import React, { memo, useState } from "react";
import styles from "./navigation.styles.scss";
const Navigation = ({ children }) => {
const [toggle, toggleState] = useState(false);
return (
<>
<div onClick={() => toggleState(!toggle)}>
<p>Test</p>
</div>
{children}
<style jsx>{styles}</style>
</>
);
};
export default memo(Navigation);
And then I have another component that looks like this:
import React, { memo, useState } from "react";
import styles from "./container.styles.scss";
const Container = ({ children }) => {
const [toggle, toggleState] = useState(false);
return (
<>
<div className={toggle ? "dark-bg" : "dark-bg active"}>
{children}
</div>
<style jsx>{styles}</style>
</>
);
};
export default Container ;
Now, the thing is the {children} of the 1st component is sometimes the 2nd component, and sometimes it's not. Therefore I can't just put the CSS and HTML from the 2ndcomponent into the 1st component - which in turn would fix my problem.
But as you might be able to see, there is an onClick event in the first component. I would like it so that when that is clicked, the state from the click is send to the 2nd component and toggles the className-toggle.
Can this be achieved by doing this, or do I have to set everything up differently ?
And yes, I am quite new to React, so please don't be harsh.
Css
I would look into better methods of applying styling with css. Not sure about your project scope/tools but typically all the css files are imported in the dom root and loaded in there. This avoids creating css files for every component.
Here's 9 ways of implementing css for react.
Passing HTML
In react if you want to render component in another component instead of passing it as a child you should import it as follows.
// replace container path with actual path of Container file
// ex './Container.js'
import Container from 'container_path.js';
Now Rendering the Component is as simple as including it in the html code.
return (
<>
<div className={toggle ? "dark-bg" : "dark-bg active"}>
<Container/>
</div>
</>
);
Here's a Stack Overflow post of users importing components using react + es6 + webpack. More information on importing components is available there.
State management
In react if you have a state that is being accessed by multiple components the standard is to keep the state in the parent component.
This way you can pass the state as a prop to any children components. You can also create a function which updates this state and pass that function as a prop to the children.
ex:
import React, { useState } from "react";
import Container from "./Container.js";
import Navigation from "./Navigation.js"
const Parent = props => {
const [toggle, toggleState] = useState(false);
return (
<div>
<Container toggleState={toggleState} toggle={toggle} />
<Navigation toggleState={toggleState} toggle={toggle} />
</div>
)
}
Before continuing working on your project I would recommend researching functional components vs class components. Here's a helpful article.
Try to wrap second component to function with state from first component as argument.
Wrapper for your second component and using for first component
const putInnerComponent = (stateFromOuterComponent) => <Container toggle={stateFromOuterComponent}/>;
<Navigation children={putInnerComponent}/>
Your first component
import React, { memo, useState } from "react";
import styles from "./navigation.styles.scss";
const Navigation = ({ children }) => {
const [toggle, toggleState] = useState(false);
return (
<>
<div onClick={() => toggleState(!toggle)}>
<p>Test</p>
</div>
{children(toggle)}
<style jsx>{styles}</style>
</>
);
};
export default memo(Navigation);
Your second component
import React, { memo, useState } from "react";
import styles from "./container.styles.scss";
const Container = ({ children, toggle }) => {
//const [toggle, toggleState] = useState(false);
return (
<>
<div className={toggle ? "dark-bg" : "dark-bg active"}>
{children}
</div>
<style jsx>{styles}</style>
</>
);
};
export default Container;

React function component with functions

Is it possible to create an instance of a function component like regular classes like c# and JAVA where you can call functions on the component? Something like:
https://codesandbox.io/s/hungry-microservice-bp292?file=/src/App.js
It must be an instance so that the component can be used multiple places with its own instance and values. Not like a static class.
import React from "react";
import "./styles.css";
import MyFunc from "./MyFunc";
export default function App() {
const addAlert = () => {
MyFunc.addAlert("dasdsad");
};
return (
<div className="App">
<button onClick={addAlert}>Add alert</button>
<MyFunc />
</div>
);
}
You are mixing different concepts of ReactJS and the underlying DOM. It is not possible to get a ref on the functional component itself. At most you can use forwardRef to get a reference to the underlying DOM element. You can read more about that Refs and the DOM and Forwarding Refs.
With that in mind you could change your approach by uplifting the state to the parent e.g.
App.js
export default function App() {
const [alerts, addAlerts] = useState(["Alert1", "Alert2"]);
const addAlert = () => {
addAlerts(alerts.concat("dasdsad"));
};
return (
<div className="App">
<button onClick={addAlert}>Add alert</button>
<MyFunc alerts={alerts}/>
</div>
);
}
MyFunc.js
const MyFunc = props => {
return (
<>
{props.alerts && props.alerts.map((alert, index) => (
<div>{alert}</div>
))}
</>
);
};
export default MyFunc;

document.getElementById() equivalent in React 2020

I have a component called Button.js that has a button that when clicked i simply would like to know if i am accessing the a div in another component called Timer.js. In vanilla javascript i would simply use document.getElementById() to capture the DOM node. How is this done in React?
I came across callback-refs in the docs but it isn't working. If using a ref isn't the React way of accessing DOM elements please refer me to the best way to do this. thanks in advance.
Button.js
function Button() {
const getHtml = () => {
const node = test.current;
console.log(node);
}
return (
<button onClick={getHtml}>GetHtml</button>
)
}
Timer.js
function Timer() {
const test = useRef(null);
return (
<div ref={test}>... </div>
<Button />
}
I would not use a reference to check if a component is rendered inside of another one.
You could get what you're looking for with createContext and useContext.
(It could work like you tried it. If you'd pass the ref to the button as a prop.)
With the context: You create a TimerContext.Provider in your Timer component and in your button you can check with useContext(TimerContext) if the expected key is in the object. If it's not there then the button is not inside of your Timer.
Please have a look at the snippet below or in the following Codesandbox.
//import React, { useContext, createContext } from "react";
//import "./styles.css";
const { useContext, createContext } = React;
const ContainerContext = createContext({
isInContainer: null
});
const Container = () => {
return (
<ContainerContext.Provider value={{ isInContainer: true }}>
<p>
In container:
<Button />
</p>
</ContainerContext.Provider>
);
};
const Button = () => {
const { isInContainer } = useContext(ContainerContext);
console.log(isInContainer);
const isInside = () => {
alert(isInContainer ? "clicked inside" : "not in container");
};
return <button onClick={isInside}>Click me</button>;
};
function App() {
return (
<div className="App">
<Container />
<Button />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
rootElement
);
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
Update 15.04.2020
The question was not clear to me at first but now I understand the use-case. The idea is to have an Editor component where you're writing markup that can be used to generate a copied snippet view and/or a html markup output.
For this the best is to use a reference to the Editor component and pass it as prop to the preview/output component - it would be also possible with a context but passing it is easier.
Like in the following Sandbox.

How to add {props.children} to a React component

i have many components which have {props.children} deeply nested inside.
considery DRY principle is there a way to add this using some React pattern.
example
let's say i have two components,
Comp1.js
import React from "react";
const Comp1 = props => {
return (
<div>
<h1>{props.children}</h1>
</div>
);
};
export default Comp1;
Comp2.js
import React from "react";
const Comp2 = props => {
return (
<div>
<div>
<h1>{props.children}</h1>
</div>
</div>
);
};
export default Comp2;
if you see above code we have both Comp1 and Comp2 have line of code {props.children} repeated inside.
what i want now is some function which will add this line of code, something like below,
const addPropsChildrenToComp = (Comp)=>{
return(
(props)=>{
///do somehting here
}
)
}
const Comp1 = props => {
return (
<div>
<h1></h1>
</div>
);
};
Comp1WithPropsChildren = addPropsChildrenToComp(Comp1)
using HOC doesn't work because, in HOC we never modify passed component.
anyway to aceve this.?
to get more idea of my problem see this demo: https://codesandbox.io/s/trusting-http-pd1yu
in there i woul like to see CompWithPropsChildren component render props.children inside it.
I think I see what you're trying to get to, and you can accomplish this just using another component.
import React from "react";
import ChildComp from "./ChildComp";
const Comp1 = props => {
return (
<div>
<ChildComp {...props} />
</div>
);
};
export default Comp1;
import React from "react";
const ChildComp = props => {
return <h1>{props.children}</h1>
}
Assuming your ChildComp has some complex logic you don't want to duplicate, this will make it reusable for you.

TypeError: Cannot read property 'map' of undefined Reactjs

Newbie to React and I need help again - everything was fine untill i shifted the code from App.js to separate component - and b/c it is the stateless component, i am using props and map function to access the value and state from App.js but it is not happy -- help please
import React from 'react';
const Recipes = props => (
<div>
{props.recipes.map(recipe => (
<div key={recipe.recipe_id}>
<img src={recipe.image_url} alt={recipe.title} />
<p>{recipe.title}</p>
</div>
))}
</div>
);
export default Recipes;
This just means that you don't pass in recipes properly as a prop where you render <Recipes />. Either recipes is null or incorrectly formatted.
ex:
// App.js
import React from 'react';
const App = () => {
const recipes = [{
recipe_id: '<id>',
image_url: '<some url>',
title: 'Lé title'
}];
// recipes could be null/undefined or not even passed as a prop
return (
<Recipes recipes={recipes} />
);
}
export default App;
So it's hard to know exactly what's happening without being able to see how you are passing down props, and exactly what data they contain. The error you are getting implies that you aren't actually sending the recipes array correctly.
I honestly never use stateless functions in react anymore, because PureComponent generally preforms better because of it's built in shouldComponentUpdate which prevents unnecessary re-renders. So here's how I would write that component:
import React, { PureComponent } from 'react';
class Recipes extends PureComonent {
recipeList = () => {
const recipes = this.props;
const recipeArray = recipes.map((recipe) => {
<div key={recipe.recipe_id}>
<img src={recipe.image_url} alt={recipe.title} />
<p>{recipe.title}</p>
</div>
});
return recipeArray;
}
render () {
return () {
<div>
{this.recipeList()}
</div>
}
}
}
export default Recipes;
That being said, my guess about the way you wrote your component is that if you were to console out props you would find that it was actually equal to recipes, which is why recipes.recipes is undefined.

Resources