Developed with react and typescript.
Now the card is shown or hidden when you click on the div tag.
I want to hide the Card when it is displayed, even if another place other than the div tag is pressed.
import React, { FunctionComponent, useState } from 'react';
import { Card } from 'components/atoms/Card';
import { Display } from 'components/atoms/Display';
const Test: FunctionComponent = () => {
const [isDisplay, setIsDisplay] = useState(false);
const onClick = () => {
setIsDisplay(!isDisplay);
};
return (
<>
<div onClick={onClick} style={{ width: '100px', height: '100px' }}>
display Card
</div>
<Display enabled={isDisplay}>
<Card width={100} height={100}></Card>
</Display>
</>
);
};
export default Test;
Try this in your onClick method. It looks like you need to access the current state's value and update it.
setIsDisplay(state => !state);
It's explained here in the React docs.
https://reactjs.org/docs/hooks-reference.html#functional-updates
Related
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>
);
}
}
import { use state } from 'react';
import React from 'react';
import './style.css';
export default function Div() {
const [data, set data] = useState('green');
return (
<div class="sec" style={{ 'background-color': data }}>
{data}
<button on Click={() => setData('red', 'orange')}>state click
);
}
First => You should remove the space between the words "set" and "data" like this:
const [data, setData] = useState('green');
Second => in the style attribute just the second argument is a string, but in this case you will have to use "Template literals" using backticks(`) like this:
<div class="sec" style={{ background-color: `${data}`}}>
I've fixed the typos and errors in your code, and it seems to be working fine.
import { useState } from "react";
import React from "react";
import "./style.css";
export default function Div() {
const [data, setData] = useState("green");
return (
<div class="sec" style={{ "background-color": data }}>
<button onClick={() => setData("orange")}>state click</button>
</div>
);
}
it changes the background color to orange when the button is clicked, but I still don't understand, do you want to change the color of the background based on an array of colors? if so you can do it using the Math.random, on each click a random item from the colors array will be generated.
import { useState } from "react";
import React from "react";
import "./style.css";
export default function Div() {
const [data, setData] = useState("green");
const handleColorChange = () => {
const colors = ["orange", "green", "red", "black"];
var colorItem = colors[Math.floor(Math.random() * colors.length)];
setData(colorItem);
};
return (
<div class="sec" style={{ "background-color": data }}>
<button onClick={() => handleColorChange}>state click</button>
</div>
);
}
Here is short explanation
First: The state is used to store things which change.
The array of colours isn't going to change, so don't store it in the state.
The selected colour is going to change, so do store that in the state. In this case, you can do that by using the index of the colour in the array.
Next, when the colour changes, you want to pick the next one. That's just a matter of incrementing the index. However, when you get to the end you'll probably want to loop back. So check for that.
Third, since you only want one button, create only one button. Don't loop over the array of colours there.
Use the value from the state to assign the background colour.
onclick is onClick in React.
Just pass the function for setting the nextColour. It doesn't need any arguments.
Finally, if you want an HTML button element then it is . Starting a JSX name with a capital letter means you are using a component. There are plenty of third-party Button components, but you aren't importing any. Trying to use here would be recursively using the component you just created.
import React, { useState } from "react";
const colours = ['red', 'green', 'blue', 'orange', 'yellow'];
export function Button(props) {
const [selectedColourIndex, setColourIndex] = useState(0);
const nextColour = () => {
const newColourIndex = selectedColourIndex + 1;
if (colours[newColourIndex])
setColourIndex(newColourIndex);
else
setColourIndex(0);
}
return (<button type="button" style={{backgroundColor: colours[selectedColourIndex]}}
onClick={nextColour}>Change color</button>);
}
I'm currently trying to animate a div so that it slides from bottom to top inside a card.
The useMeasure hook is supposed to give me the height of the wrapper through the handler I attached to it : <div className="desc-wrapper" {...bind}>
Then I am supposed to set the top offset of an absolutely positionned div to the height of its parent and update this value to animate it.
The problem is that when logging the bounds returned by the useMeasure() hook, all the values are at zero...
Here is a link to production exemple of the panel not being slided down because detected height of parent is 0 : https://next-portfolio-41pk0s1nc.vercel.app/page-projects
The card component is called Project, here is the code :
import React, { useEffect, useState } from 'react'
import './project.scss'
import useMeasure from 'react-use-measure';
import { useSpring, animated } from "react-spring";
const Project = (projectData, key) => {
const { project } = projectData
const [open, toggle] = useState(false)
const [bind, bounds] = useMeasure()
const props = useSpring({ top: open ? 0 : bounds.height })
useEffect(() => {
console.log(bounds)
})
return (
<div className="project-container">
<div className="img-wrapper" style={{ background: `url('${project.illustrationPath}') no-
repeat center`, backgroundSize: project.portrait ? 'contain' : 'cover' }}>
</div>
<div className="desc-wrapper" {...bind} >
<h2 className="titre">{project.projectName}</h2>
<span className="description">{project.description}</span>
<animated.div className="tags-wrapper" style={{ top: props.top }}>
</animated.div>
</div>
</div>
);
};
export default Project;
Is this a design issue from nextJS or am I doing something wrong ? Thanks
I never used react-use-measure, but in the documentations, the first item in the array is a ref and you are suppose to use it this way.
function App() {
const [ref, bounds] = useMeasure()
// consider that knowing bounds is only possible *after* the view renders
// so you'll get zero values on the first run and be informed later
return <div ref={ref} />
}
You did...
<div className="desc-wrapper" {...bind} >
Which I don't think is correct...
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>
);
}
}
I have a <Panel/> component that has to get different changing components.
For example one time- the <Panel/> needs to contain a <Dropdown/> component, a second time a <TextInput/> and in the third time a <Checkbox/>.
How can I make this <Panel/> component to get different components?
The <Panel/> component:
import React from "react";
import { css } from "emotion";
import colors from '../../styles/colors';
import PanelHeader from "./PanelHeader";
export default function Panel({ active, panelHeader}) {
const styles = css({
borderRadius: 4,
backgroundColor: "white",
border: `1px solid ${ active ? colors.blue : colors.grayLight }`,
width: 540,
padding: 32,
});
return (
<div className={styles}>
{panelHeader && <PanelHeader headerType={panelHeader} />}
</div>
);
}
The Panel story:
import React from "react";
import { storiesOf } from "#storybook/react";
import Panel from "../components/Panel";
import colors from '../styles/colors';
import PanelHeader from "../components/Panel/PanelHeader";
storiesOf("Panel", module)
.add("Default", () => (
<Panel></Panel>
))
.add("Active", () => (
<Panel active></Panel>
))
storiesOf("Panel/PanelHeader", module)
.add("Default", () => (
<PanelHeader headerType="Identity document" color={colors.gray}>1</PanelHeader>
))
.add("Active", () => (
<PanelHeader headerType="Identity document" color={colors.blue}>1</PanelHeader>
))
You can change Panel to accept children prop, pass it where you Render <Panel> and pass in the corresponding component.
For example:
// PropType for children is `PropTypes.node`
export default function Panel({ active, panelHeader, children}) {
// ...
return (
<div className={styles}>
{children}
</div>
);
}
// ...
<Panel><Dropdown /></Panel>
// or
<Panel><TextInput /></Panel>
Or, you could pass in a component class/function and render it inside:
export default function Panel({ active, panelHeader, ChildComponent}) {
// ...
return (
<div className={styles}>
{/* This is like any other component,
you can pass in props as usual. */}
{/* It's important for the name to start with an uppercase letter,
otherwise the JSX compiler will turn this in a string! */}
<ChildComponent />
</div>
);
}
// ...
<Panel ChildComponent={Dropdown}></Panel>
// or
<Panel ChildComponent={TextInput}></Panel>
This pattern is called component composition. You can read more in React docs: https://reactjs.org/docs/composition-vs-inheritance.html