React JS // Editing style of one component from another component - reactjs

My goal is to create a basic app that allows me to change the style of one component with an action from another component.
Lets assume I have a <Btn/> component and a <Box/> component and when the button is clicked, I want to change the background color of the box. <Btn/> and <Box/> have the common ancestor of <App/> but are both at different levels in the component tree.
Btn.js
import React from 'react'
function Btn() {
const handleClick = (e) => {
//...
}
return (
<button onClick={handleClick}>
Click me
</button>
);
}
export default Btn
Box.js
import React from 'react'
function Box() {
return (
<h1>
Hello World!
</h1>
);
}
export default Box
I do not want to use prop drilling (with style setting/getting functionality in the <App/> component) to achieve this. I have also deliberately left out component styling as I am open to whichever styling option is best to solve this problem.
What would be the best way to go about this? (I'm open to using Context, Redux or another library if it is appropriate.)

The simplest way of doing this is with Context, as you're using function components not classes the documentation you'll need is useContext https://reactjs.org/docs/hooks-reference.html#usecontext. You still have to define the prop and "setter" function at the app level or at a component called at the app level, but with context you don't have to pass the props all the way down.
To take their example and adapt it to your use case would go something like this. (Working sample: https://codesandbox.io/s/stackoverflow-answer-7hryk)
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = React.createContext(themes.light);
function App() {
const [stateTheme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme: themes[stateTheme], setTheme: setStateTheme }}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ToggleButtons />
<ThemedButton />
</div>
);
}
function ToggleButtons() {
const { setTheme } = useContext(ThemeContext);
return (
<div>
<button onClick={() => setTheme('light')}>Light Theme</button>
<button onClick={() => setTheme('dark')}>Dark Theme</button>
</div>
);
}
function ThemedButton() {
const { theme } = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}

Related

React Beginner - Trouble understanding useContext for dark mode

I'm completing an online program to learn ReactJS. After going over useState we are now learning useContext. Below I'll go over my current understanding of how useContext works, and where I'm facing trouble.
The goal is a simple page with a light/dark mode switch
What I currently understand as the "steps" to using useContext:
Import and initialize createContext
Wrap child components with Provider
Import useContext hook from React so we can use the Context in child components
Access the user Context in desired component(s)
But I'm facing an issue with understanding the code block below
This is the solution to a file named ThemeContext.js.
import { createContext, useContext, useState } from "react";
const ThemeContext = createContext(undefined);
export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider
value={{
theme,
toggleTheme: () => setTheme(theme === "light" ? "dark" : "light"),
}}
>
{children}
</ThemeContext.Provider>
);
};
export const useTheme = () => useContext(ThemeContext);
This is the solution to App.js.
import "./App.css";
import { ThemeProvider, useTheme } from "./ThemeContext";
import Switch from "./Switch";
const Title = ({ children }) => {
const { theme } = useTheme();
return (
<h2
style={{
color: theme === "light" ? "black" : "white",
}}
>
{children}
</h2>
);
};
const Paragraph = ({ children }) => {
const { theme } = useTheme();
return (
<p
style={{
color: theme === "light" ? "black" : "white",
}}
>
{children}
</p>
);
};
const Content = () => {
return (
<div>
<Paragraph>
We are a pizza loving family. And for years, I searched and searched and
searched for the perfect pizza dough recipe. I tried dozens, or more.
And while some were good, none of them were that recipe that would
make me stop trying all of the others.
</Paragraph>
</div>
);
};
const Header = () => {
return (
<header>
<Title>Little Lemon 🍕</Title>
<Switch />
</header>
);
};
const Page = () => {
return (
<div className="Page">
<Title>When it comes to dough</Title>
<Content />
</div>
);
};
function App() {
const { theme } = useTheme();
return (
<div
className="App"
style={{
backgroundColor: theme === "light" ? "white" : "black",
}}
>
<Header />
<Page />
</div>
);
}
function Root() {
return (
<ThemeProvider>
<App />
</ThemeProvider>
);
}
export default Root;
Finally, this is the solution to index.js
import "./Styles.css";
import { useTheme } from "../ThemeContext";
const Switch = () => {
const { theme, toggleTheme } = useTheme();
return (
<label className="switch">
<input
type="checkbox"
checked={theme === "light"}
onChange={toggleTheme}
/>
<span className="slider round" />
</label>
);
};
export default Switch;
My questions begin here
Instead of directly wrapping children with Provider, they instead create ThemeProvider that then returns ThemeContext.Provider. Why is this? and why is { children } necessary as seen in App.js along with the return ThemeContext return statement?
This exercise goes beyond what I believe was taught in the lesson, so I could have some holes to fill in my knowledge as far as using { children } along with the use of ThemeProvider. Normally it's demonstrated as <ThemeContext.Provider> wrapping children on the inside. In App it looks like they don't do this, but it's done in the Root, and maybe since they're wrapping App that's why { children } is indicated? I'm not certain about this and I'd just like to know why things were done specifically like this (again, this is unlike what was demonstrated in past exercises). First post, thanks in advance.
EDIT: After looking more into this issue I'm starting to come around and understand how they came up with this solution. One of the few things they didn't do previously that was used in this example was the use of ({ children }). This caused confusion for me at first, but I've come closer to understanding its usage. For example, its use in the Paragraph component:
const Paragraph = ({ children }) => {
const { theme } = useTheme();
return (
<p
style={{
color: theme === "light" ? "black" : "white",
}}
>
{children}
</p>
);
};
Which is later referenced in the Content component as such:
<Paragraph>
We are a pizza loving family. And for years, I searched and searched and
searched for the perfect pizza dough recipe. I tried dozens, or more.
And while some were good, none of them were that recipe that would
make me stop trying all of the others.
</Paragraph>
This simply means to take the children of the Paragraph component and return the information styled as such. Whatever comes inside of Paragraph, in this case a block of text, was returned with the intended style. I thought of deleting this post but maybe it will help someone else. Not sure if adding more about what I learned here would be excessive, and I'm still wrapping my head around the rest of the issue so documenting here isn't my top priority as of now.
React Context is a relatively new feature that the React team introduced to enable an alternative to holding state in a component and prop drilling. The context provider is a node that holds a value, which can be accessed by any node rendered under it (any children or children of children) via the createContext() with useContext or Context.Consumer. When the value of the Provider changes, any component subscribed with useContext or rendered by Consumer will be rerendered.
There is no practical difference between using the provider directly in index.js. What matters is who is rendered under the provider. Keep in mind that JSX's <ThemeProvider> is in reality a call to React.createElement with App in the children argument.
Why create a ThemeProvider? It packages the provider with state. The alternative would be to use the provider directly in Root and create useState there, but it is inflexible and not reusable.
const ThemeContext = createContext(undefined);
function Root() {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider
value={{
theme,
toggleTheme: () => setTheme(theme === "light" ? "dark" : "light"),
}}
>
<App />
</ThemeContext.Provider>
);
}
Note that App is still the children of the provider.

Correct use of ReactToPrint?

The problem is that the button that is supposed to give the option to print is not working anymore.
the error in the console says:
To print a functional component ensure it is wrapped with `React.forwardRef`, and ensure the forwarded ref is used. See the README for an example: https://github.com/gregnb/react-to-print#examples
I Have already seen some solutions specifically talking about the same problem but I have not been able to make it work.
any suggestion?
this is the library i'm using: ReactToPrint npm
React To print
import { useRef } from "react";
import { useReactToPrint } from "react-to-print";
import Resume from "./Pdf/Pdf";
const Example = () => {
const componentRef = useRef();
const handlePrint = useReactToPrint({
content: () => componentRef.current
});
return (
<div >
<button onClick={handlePrint}> ------> NOT WORKING!
Descargar Pdf
</button>
<Resume ref={componentRef} /> ------> COMPONENT TO PRINT
</div>
);
};
export default Example;
Component to be printed
import React from "react";
import styled from 'styled-components';
import PdfSection from './PdfSection';
import AlienLevel from './AlienLevel';
import {connect } from 'react-redux';
class Resume extends React.Component {
renderList = () => {
return this.props.posts.diagnose.map((post) => {
return (
<PdfSection
key={post.id}
id={post.id}
divider={"/images/pdf/divider.png"}
img={"/images/alienRandom.png"}
title={post.title}
// data={post.data}
text={post.text0}
subtext={post.subtext0}
/>
);
});
};
render(){
return (
<div>
<Container>
<Page>
<Portada>
<img id="portada" src="/images/pdf/PortadaPdf.png" />
</Portada>
</Page>
<Page>
<AlienLevel
result= "{props.diagn}{"
character={"/images/pdf/alienMedio.png"}
fileName={"responseBody[4].data"}
level={"/images/pdf/level6.png"}
correct={"/images/pdf/correct.png"}
medium={"/images/pdf/medium.png"}
incorrect={"/images/pdf/incorrect.png"}
text='"Necesitas mejorar tus prácticas intergalácticas de CV, pero ya eres nivel medio!"'
/>
<div>{this.renderList()}</div>
</Page>
</Container>
</div>
);
};
}
const mapStateToProps = (state) => {
return { posts: state.posts };
};
export default connect(mapStateToProps)( Resume);
thanks in advance!
The problem is with connect() function of react-redux.
You wrapped your component in connect and connect by default does not forward ref. Which means, the ref you are passing here <Resume ref={componentRef} /> does not reach to your component.
You need to pass options { forwardRef: true } in fourth parameter of connect function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?).
Just change this code export default connect(mapStateToProps)(Resume); in Resume component to this
export default connect(mapStateToProps, null, null, { forwardRef: true })(Resume);
For anyone that is struggling with the same error, it seems that they found the proper way to resolve this, I actually resolved it by following the Codesandbox I found in the Github issues here si the link. hope is useful! -->
LINK TO GITHUB SPECIFIC ISSUE (SOLVED!!)
I had the same issue and I am happy to share my findings as soon as now.
The component has to be rendered somewhere using ref.
I added it to my page as hidden using React Material UI's Backdrop. Or u can hide it using hooks like examples below.
Using backdrop and only calling it when I need to preview the print. 👇👇
<Backdrop sx={{ color: "#fff", zIndex: (theme) => theme.zIndex.drawer + 1 }}
open={openBD}>
<ComponentToPrint ref={componentRef} />
</Backdrop>
Using Hooks plus display styling to only display it when needed. 👇👇
const [isReady, setIsReady] = useState("none");
<Paper style={{ display: isReady }} >
<ComponentToPrint ref={componentRef} />
</Paper>
<Button
variant="contained"
endIcon={<BackupTableRoundedIcon />}
onClick={() => setIsReady("")}
>
Start Printing
</Button>
Note: I used MUI components, if u decide to copy paste, then change Button to html <button and paper to <div. Hope this helps.

Material UI override react subtree except for certain components

Is there a way to override the theme of a react sub-tree, but skip the override for certain components?
I'm overriding all children of a component, making sure all the font size is small, using:
const overrideTheme = (theme: Theme): Theme => {
return createTheme({
...theme,
typography: {
fontSize: 11,
}
})
}
const MyCustomThemeWrapper = ({children}) => {
return (
<div>
<ThemeProvider theme={overrideTheme}>
{children}
</ThemeProvider>
</div>
)
}
What I want to do is make this override exclude certain components (and their children). In particular, I don't want to override the font size of any Dialog components that's part of the children subtree. Any way I can do this, without having to "re-override" the theme for each Dialog? I.e. I want something this:
const Component1 = () => {
return (
<MyCustomThemeWrapper>
<div>
This text has fontSize 11
<span> more font size 11</span>
<Dialog open={open}>
<DialogContent>
This text has the "original" fontSize
</DialogContent>
</Dialog>
</div>
</MyCustomThemeWrapper>
)
}
where the custom theme override "hits" all the children of MyCustomThemeWrapper except for the Dialog subtree. I know I'm asking for much here, but would be really nice if this was possible.
You can override inline component's styles with wrapping another ThemeProvider as a parent to inline components. For instance, If you would like to except the Dialog component from override ThemeProvider, You should wrap it with another ThemeProvider. Because of your code wasn't clear enough to describing on it, I've made another example which in that inline checkboxs styles, override by another ThemeProvider:
import React from "react";
import { createTheme, ThemeProvider } from "#material-ui/core/styles";
import Checkbox from "#material-ui/core/Checkbox";
import { green, orange } from "#material-ui/core/colors";
const outerTheme = createTheme({
palette: {
secondary: {
main: orange[500]
}
}
});
const innerTheme = createTheme({
palette: {
secondary: {
main: green[500]
}
}
});
const CustomCheckBox = () => {
return (
<ThemeProvider theme={innerTheme}>
<Checkbox defaultChecked />
</ThemeProvider>
);
};
export default function App() {
return (
<ThemeProvider theme={outerTheme}>
<Checkbox defaultChecked />
<Checkbox defaultChecked />
<CustomCheckBox />
</ThemeProvider>
);
}
Here's the result:

When migrating to Material-UI v5, how to deal with conditional classes?

In the official migration guide, they give the following example of changing the code from JSS (makeStyles) to the new styled mode.
Before:
const useStyles = makeStyles((theme) => ({
background: theme.palette.primary.main,
}));
function Component() {
const classes = useStyles();
return <div className={classes.root} />
}
After:
const MyComponent = styled('div')(({ theme }) =>
({ background: theme.palette.primary.main }));
function App(props) {
return (
<ThemeProvider theme={theme}>
<MyComponent {...props} />
</ThemeProvider>
);
}
This is fine for a single class, but what to do when the code has conditional classes?
e.g.
<main className={classnames(content, open ? contentOpen : contentClosed)}>
{/* content goes here */}
</main>
Here, content, contentOpen, and contentClosed are classes returned from useStyles, but contentOpen and contentClosed are rendered conditionally based on the value of open.
With the new styled method, instead of generating class names we're generating components. Is there a way to elegantly replicate the rendering without resorting to content duplication in the ternary expression?
e.g. we don't want to do something like:
function App(props) {
return (
<ThemeProvider theme={theme}>
{open
? <MyOpenComponent {...props}>{/* content */}</MyOpenComponent>
: <MyClosedComponent {...props}>{/* content */}</MyClosedComponent>
</ThemeProvider>
);
}
There are quite a few possible ways to deal with this. One approach using styled is to leverage props to do dynamic styles rather than trying to use multiple classes.
Here's an example:
import React from "react";
import Button from "#mui/material/Button";
import { styled } from "#mui/material/styles";
const StyledDiv = styled("div")(({ open, theme }) => {
const color = open
? theme.palette.primary.contrastText
: theme.palette.secondary.contrastText;
return {
backgroundColor: open
? theme.palette.primary.main
: theme.palette.secondary.main,
color,
padding: theme.spacing(0, 1),
"& button": {
color
}
};
});
export default function App() {
const [open, setOpen] = React.useState(false);
return (
<StyledDiv open={open}>
<h1>{open ? "Open" : "Closed"}</h1>
<Button onClick={() => setOpen(!open)}>Toggle</Button>
</StyledDiv>
);
}
Here's an equivalent example using TypeScript:
import * as React from "react";
import Button from "#mui/material/Button";
import { styled } from "#mui/material/styles";
const StyledDiv: React.ComponentType<{ open: boolean }> = styled("div")(
({ open, theme }) => {
const color = open
? theme.palette.primary.contrastText
: theme.palette.secondary.contrastText;
return {
backgroundColor: open
? theme.palette.primary.main
: theme.palette.secondary.main,
color,
padding: theme.spacing(0, 1),
"& button": {
color
}
};
}
);
export default function App() {
const [open, setOpen] = React.useState(false);
return (
<StyledDiv open={open}>
<h1>{open ? "Open" : "Closed"}</h1>
<Button onClick={() => setOpen(!open)}>Toggle</Button>
</StyledDiv>
);
}
Some other possible approaches:
Use Emotion's css prop and Emotion's capabilities for composing styles
Use tss-react to retain similar syntax to makeStyles but backed by Emotion (so you wouldn't be including both Emotion and JSS in your bundle as would be the case if you leverage makeStyles from #material-ui/styles). This is the route I took when migrating to v5 and as part of my migration I created a codemod for migrating JSS makeStyles to tss-react's makeStyles.

How do I avoid 'Function components cannot be given refs' when using react-router-dom?

I have the following (using Material UI)....
import React from "react";
import { NavLink } from "react-router-dom";
import Tabs from "#material-ui/core/Tabs";
import Tab from "#material-ui/core/Tab";
function LinkTab(link){
return <Tab component={NavLink}
to={link.link}
label={link.label}
value={link.link}
key={link.link}
/>;
}
In the new versions this causes the following warning...
Warning: Function components cannot be given refs. Attempts to access
this ref will fail. Did you mean to use React.forwardRef()?
Check the render method of ForwardRef.
in NavLink (created by ForwardRef)
I tried changing to...
function LinkTab(link){
// See https://material-ui.com/guides/composition/#caveat-with-refs
const MyLink = React.forwardRef((props, ref) => <NavLink {...props} ref={ref} />);
return <Tab component={MyLink}
to={link.link}
label={link.label}
value={link.link}
key={link.link}
/>;
}
But I still get the warning. How do I resolve this issue?
Just give it as innerRef,
// Client.js
<Input innerRef={inputRef} />
Use it as ref.
// Input.js
const Input = ({ innerRef }) => {
return (
<div>
<input ref={innerRef} />
</div>
)
}
NavLink from react-router is a function component that is a specialized version of Link which exposes a innerRef prop for that purpose.
// required for react-router-dom < 6.0.0
// see https://github.com/ReactTraining/react-router/issues/6056#issuecomment-435524678
const MyLink = React.forwardRef((props, ref) => <NavLink innerRef={ref} {...props} />);
You could've also searched our docs for react-router which leads you to https://mui.com/getting-started/faq/#how-do-i-use-react-router which links to https://mui.com/components/buttons/#third-party-routing-library. The last link provides a working example and also explains how this will likely change in react-router v6
You can use refs instead of ref. This only works as it avoids the special prop name ref.
<InputText
label="Phone Number"
name="phoneNumber"
refs={register({ required: true })}
error={errors.phoneNumber ? true : false}
icon={MailIcon}
/>
In our case, we were was passing an SVG component (Site's Logo) directly to NextJS's Link Component which was a bit customized and we were getting such error.
Header component where SVG was used and was "causing" the issue.
import Logo from '_public/logos/logo.svg'
import Link from '_components/link/Link'
const Header = () => (
<div className={s.headerLogo}>
<Link href={'/'}>
<Logo />
</Link>
</div>
)
Error Message on Console
Function components cannot be given refs. Attempts to access this ref will fail.
Did you mean to use React.forwardRef()?
Customized Link Component
import NextLink from 'next/link'
import { forwardRef } from 'react'
const Link = ({ href, shallow, replace, children, passHref, className }, ref) => {
return href ? (
<NextLink
href={href}
passHref={passHref}
scroll={false}
shallow={shallow}
replace={replace}
prefetch={false}
className={className}
>
{children}
</NextLink>
) : (
<div className={className}>{children}</div>
)
}
export default forwardRef(Link)
Now we made sure we were using forwardRef in the our customized Link Component but we still got that error.
In order to solve it, I changed the wrapper positioning of SVG element to this and :poof:
const Header = () => (
<Link href={'/'}>
<div className={s.headerLogo}>
<Logo />
</div>
</Link>
)
If you find that you cannot add a custom ref prop or forwardRef to a component, I have a trick to still get a ref object for your functional component.
Suppose you want to add ref to a custom functional component like:
const ref = useRef();
//throws error as Button is a functional component without ref prop
return <Button ref={ref}>Hi</Button>;
You can wrap it in a generic html element and set ref on that.
const ref = useRef();
// This ref works. To get button html element inside div, you can do
const buttonRef = ref.current && ref.current.children[0];
return (
<div ref={ref}>
<Button>Hi</Button>
</div>
);
Of course manage state accordingly and where you want to use the buttonRef object.
to fix this warning you should wrap your custom component with the forwardRef function as mentioned in this blog very nicely
const AppTextField =(props) {return(/*your component*/)}
change the above code to
const AppTextField = forwardRef((props,ref) {return(/*your component*/)}
const renderItem = ({ item, index }) => {
return (
<>
<Item
key={item.Id}
item={item}
index={index}
/>
</>
);
};
Use Fragment to solve React.forwardRef()? warning
If you're using functional components, then React.forwardRef is a really nice feature to know how to use for scenarios like this. If whoever ends up reading this is the more hands on type, I threw together a codesandbox for you to play around with. Sometimes it doesn't load the Styled-Components initially, so you may need to refresh the inline browser when the sandbox loads.
https://codesandbox.io/s/react-forwardref-example-15ql9t?file=/src/App.tsx
// MyAwesomeInput.tsx
import React from "react";
import { TextInput, TextInputProps } from "react-native";
import styled from "styled-components/native";
const Wrapper = styled.View`
width: 100%;
padding-bottom: 10px;
`;
const InputStyled = styled.TextInput`
width: 100%;
height: 50px;
border: 1px solid grey;
text-indent: 5px;
`;
// Created an interface to extend the TextInputProps, allowing access to all of its properties
// from the object that is created from Styled-Components.
//
// I also define the type that the forwarded ref will be.
interface AwesomeInputProps extends TextInputProps {
someProp?: boolean;
ref?: React.Ref<TextInput>;
}
// Created the functional component with the prop type created above.
//
// Notice the end of the line, where you wrap everything in the React.forwardRef().
// This makes it take one more parameter, called ref. I showed what it looks like
// if you are a fan of destructuring.
const MyAwesomeInput: React.FC<AwesomeInputProps> = React.forwardRef( // <-- This wraps the entire component, starting here.
({ someProp, ...props }, ref) => {
return (
<Wrapper>
<InputStyled {...props} ref={ref} />
</Wrapper>
);
}); // <-- And ending down here.
export default MyAwesomeInput;
Then on the calling screen, you'll create your ref variable and pass it into the ref field on the component.
// App.tsx
import React from "react";
import { StyleSheet, Text, TextInput, View } from "react-native";
import MyAwesomeInput from "./Components/MyAwesomeInput";
const App: React.FC = () => {
// Set some state fields for the inputs.
const [field1, setField1] = React.useState("");
const [field2, setField2] = React.useState("");
// Created the ref variable that we'll use down below.
const field2Ref = React.useRef<TextInput>(null);
return (
<View style={styles.app}>
<Text>React.forwardRef Example</Text>
<View>
<MyAwesomeInput
value={field1}
onChangeText={setField1}
placeholder="field 1"
// When you're done typing in this field, and you hit enter or click next on a phone,
// this makes it focus the Ref field.
onSubmitEditing={() => {
field2Ref.current.focus();
}}
/>
<MyAwesomeInput
// Pass the ref variable that's created above to the MyAwesomeInput field of choice.
// Everything should work if you have it setup right.
ref={field2Ref}
value={field2}
onChangeText={setField2}
placeholder="field 2"
/>
</View>
</View>
);
};
const styles = StyleSheet.create({
app: {
flex: 1,
justifyContent: "center",
alignItems: "center"
}
});
export default App;
It's that simple! No matter where you place the MyAwesomeInput component, you'll be able to use a ref.
I just paste here skychavda solution, as it provide a ref to a child : so you can call child method or child ref from parent directly, without any warn.
source: https://github.com/reactjs/reactjs.org/issues/2120
/* Child.jsx */
import React from 'react'
class Child extends React.Component {
componentDidMount() {
const { childRef } = this.props;
childRef(this);
}
componentWillUnmount() {
const { childRef } = this.props;
childRef(undefined);
}
alertMessage() {
window.alert('called from parent component');
}
render() {
return <h1>Hello World!</h1>
}
}
export default Child;
/* Parent.jsx */
import React from 'react';
import Child from './Child';
class Parent extends React.Component {
onClick = () => {
this.child.alertMessage(); // do stuff
}
render() {
return (
<div>
<Child childRef={ref => (this.child = ref)} />
<button onClick={this.onClick}>Child.alertMessage()</button>
</div>
);
}
}

Resources