How to lift a REACT state? - javascript

all.
I'm learning REACT and came upon a problem.
Basically, I want to move an "handle state" function to the parent component (App), and call it inside the Child component (MenuItem).
In the App Component I create a function "handleClickFavorite" that handles the state of the variable "isFavorite".
In the MenuItem Component I pass both the function and the variable as props and use them in a onClick event. Basically, I want to change between two CSS classes (Favorite and NotFavorite) of the item everytime I click the button or div.
The MenuList part just takes the elements of an array , which are rendered individually in MenuItem, and maps them
App component:
import React, { useState } from 'react';
import './App.css';
import MenuList from './components/MenuList';
import foodItems from './components/data.js';
const App = (props) => {
const [isFavorite, setIsFavorite] = useState(props.isFavorite);
const handleClickFavorite = () => {
setIsFavorite(!isFavorite);
};
return (
<div>
<h1>Wild Restaurant Menu</h1>
<MenuList
isFavorite={isFavorite}
handleClickFavorite={handleClickFavorite}
foodItems={foodItems}
/>
</div>
);
};
export default App;
MenuList Component:
import React from 'react';
import MenuItem from './MenuItem';
function MenuList({ foodItems }) {
console.log(foodItems);
return (
<div>
{foodItems.map((element, index) => (
<MenuItem {...element} key={index} />
))}
</div>
);
}
export default MenuList;
MenuItem Component:
import React, { useState } from 'react';
import '../App.css';
function MenuItem(props) {
//create a state isFavorite that has the inital value of isFavorite that comes from the props
const {
itemName,
description,
foodImage,
price,
isFavorite,
handleClickFavorite,
} = props;
return (
<div className="itemContainer">
<div className="leftContainer">
<div className="imgContainer">
{/* the image will receive the url src from the props */}
<img src={foodImage} alt="" />
</div>
<div className="itemDescription">
{/* the h3 will receive the item name from the props */}
<h3>{itemName}</h3>
{/* the p will receive the item description from the props */}
<p>{description}</p>
</div>
</div>
<div className="rightContainer">
{/* the div will receive the item price from the props */}
<div>{price} EUR</div>
{/* the div with id favorite will have 2 attributes:
- onClick, will call the method handleClickFavorite,
- classname, that will be conditionally rendered, depending on the value of isFavorite from the component's state
*/}
<div
id="favorite"
onClick={handleClickFavorite}
className={isFavorite ? 'isFavorite' : 'notFavorite'}
/>
</div>
</div>
);
}
export default MenuItem;
It's my first time asking a question here, so pls be gentle. I tried many soluctions, but unfortunately nothing is working. The state of isFavorite doesn't change, and neither the classes.
Can someone help me?

You didn't actually pass props into children. In your MenuList component, you have only foodItems as props. So you can't get other props what you need. These are isFavorite and handleClickFavorite.
Your code block and change.
function MenuList({ foodItems }) {
console.log(foodItems);
return (
<div>
{foodItems.map((element, index) => (
<MenuItem {...element} key={index} />
))}
</div>
);
}
// Updated code
function MenuList({ foodItems, isFavorite, handleClickFavorite}) {
console.log(foodItems);
return (
<div>
{foodItems.map((element, index) => (
<MenuItem {...element} key={index}
isFavorite={isFavorite}
handleClickFavorite={handleClickFavorite}
/>
))}
</div>
);
}
Additionally, if you have a tree with many depths, you need to pass all props for all children at each level. This may be irritating and reduce the code quality in terms of performance and code readability.
In this case, you can use React context API or 3rd party libraries such as redux.
And you spread element into props by {...element} in MenuItem component in <MenuItem {...element} key={index} />.
But I think this may be not good because MenuItem may have more props or data in practice. So I recommend to use it like <MenuItem key={index} element={element} />.
Then you can access element in MenuItem like follows.
function MenuItem(props) {
//create a state isFavorite that has the inital value of isFavorite that comes from the props
const {
element: {
itemName,
description,
foodImage,
price,
},
isFavorite,
handleClickFavorite,
} = props;
...
}
Hope this will be helpful!

The reason why you code works no such as you want because you have not written props to MenuItem.
You have done right writing props to MenuList, so you need to dispatch useState isFavorite and setIsFavorite from MenuList to MenuItem the same way.
Just write in function MenuList({ foodItems }) also isFavorite and setIsFavorite.
So it will be function MenuList({ foodItems, isFavorite, setIsFavorite })
and dispatch them as props to MenuItem, as you have done to MenuList.
Some note. If you don't like 2-level props drilling, you can pat attention to state managers (redux, mob x, effector) or to hook useContext. So you can get data among all your project without props.

Related

how do i pass a the input value of the textfield from some component to another component in reactjs?

I am trying to pass the value of the text area from some component in reactjs to be used in another react component. the component value is stored in the first component in a useState hook so I want to access it in another component and run map() function around it . Is this possible in reactjs ? I don't want to put the whole thing in app.js because that is just plain HTML which I don't want. I want to use reactjs function components instead ?
first component:
import React, { useState, useRef, useEffect } from "react";
function Firstcomp() {
const [quotes, setQuotes] = useState(["hi there", "greetings"]);
const reference = useRef();
function sub(event) {
event.preventDefault();
setQuotes((old) => [reference.current.value, ...old]);
console.log(quotes);
return;
}
return (
<>
<div>
<div>
<div>
<h4>jon snow</h4>
</div>
<form onSubmit={sub}>
<textarea
type="textarea"
ref={reference}
placeholder="Type your tweet..."
/>
<button type="submit">Tweet</button>
</form>
{quotes.map((item) => (
<li key={item}>{item}</li>
))}
{/* we can use card display taking item as prop where it
will do the job of filling the <p> in card entry */}
</div>
</div>
</>
);
}
export default Firstcomp;
second component
import React from "react";
function SecondComp(props) {
return (
<div>
<p>{props.message}</p>
</div>
);
}
export default Secondcomp;
Use a global management state like Recoil, Redux ot Context
import React from 'react';
export const UserContext = React.createContext();
export default function App() {
return (
<UserContext.Provider value="Reed">
<User />
</UserContext.Provider>
)
}
function User() {
const value = React.useContext(UserContext);
return <h1>{value}</h1>;
}
on the exemple above we used useContext hook to provide a global variable "value", even its not declared directly in User component, but you can use it by calling the useContext hook.
in this exemple the return value in the user component is "Reed"

Why onClick event doesn't change the props.title in React

I wanna change the title by clicking the button but it doesn't change, can I have an explanation why is that happens?
import './ExpenseItem.css';
import ExpenseDate from './ExpenseDate';
import Card from './Card';
function ExpenseItem(props){
let title = props.expenseTitle;
function clickedFunc(){
title = "Update!";
}
return(
<Card className='expense-item'>
<ExpenseDate expenseDate={props.expenseDate}></ExpenseDate>
<div className='expense-item__description'>
<h2>{title}</h2>
<div className='expense-item__price'>
₹{props.expenseAmount}
</div>
</div>
<button onClick={clickedFunc}>Click</button>
</Card>
);
}
export default ExpenseItem;
This is not how data is handled with React.
The title should be stored in a state variable (see useState).
Once the data is stored in a state variable, you will have to set it with setState. When setState is called in React, the component holding the state variable re-renders. This will in turn cause your ExpenseItem component to re-render because it is a child component of whatever higher level component passed it props.
In your parent component, you should see something like:
require { useState } from 'react';
const ParentComponent = (props) => {
const [title, setTitle] = useState('Original Title');
...
...
...
return (
<div className="ParentComponent">
<ExpenseItem
title={title}
setTitle={setTitle}
expenseAmount={expenseAmount}
/>
</div>
)
}
Then, in your clickedFunc() function:
function clickedFunc() {
props.setTitle("Update!");
}

Is there a way to export setState outside stateless components in react?

Consider this example
export function InsideHoc(props){
const [A, setA] = useState(false);
return({if(A) && (<h1>Print something</h1>)});
}
In another file
import {A, setA} from './inside-file';
function ToggleFromOutside(){
return(<p onClick={setA(!A)}>Verify</p>);
}
Can setA be exposed outside so that the state of this component be changed from outside? I know this can be done through redux. But without using this, is there a way to change the state of one component?
Structure is like this
import {withCreateHOC} from './external';
import childComponent from './child';
class A extends React.Component {
render(){
<Another menus={(item) => <MenuItems object={item} />}
/>
}
}
export default withCreateHOC(A, {custom: childComponent, title: 'Add'});
//withCreateHOC renders modal here as well as it has a button to toggle the state. Same state should be used from below function
function MenuItems(){
return(<button onClick={displayModal}>)
}
Yes.
You can lift the state in that case. This works good if you don't need to pass down the setState to far down the tree.
If you don't want to pass the setState function all the way down the React element tree, you will need to use context, redux or some other state handling.
Example state lift
export function Parent(){
const [message, setMessage] = useState("Hello World");
return (
<>
<Child1 message={message} />
<Child2 changeMessage={setMessage} />
</>
);
}
// Can be in other file
function Child1(props){
return(<p>{props.message}</p>);
}
// Can be in other file
function Child2(props){
return(
<a onClick={() => props.changeMessage("Changed")}>
I can change things in other components.
</a>
);
}
Example of React tree with shared context/redux
<WithRedux>
<App>
<Navigation />
<Modal />
<PageRenderer />
<SomeList>
<ListItem />
<ListItem />
</Somelist>
</App>
<WithRedux>
All the children of the WithRedux component can access the state and modify it.
(PS: You can wrap the App with the HOC withRedux etc, this example is just for visualization)

MaterialUI Redux connect() using TextArea and submitting form. Warning about refs in func components [duplicate]

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>
);
}
}

Passing props twice for a Higher Order Component?

I'm building a webpage and realized a common style shared by each component (same background, border, and title style). So I thought I should make an HOC which accepts the inner content of each component as well as a title, and returns an outer component which wraps this inner component and heading.
At first I ran into a lot of issues trying to get this to work, being new to React, but now it's finally working but I still don't understand how.
Here is my HOC
const BaseBlock = (WrappedComponent) => {
return class BaseBlock extends Component {
render () {
return (
<div className={styles['base-block']}>
<div className={styles['container']}>
<div className={styles['base-block-head']}>
{ this.props.title }
</div>
<div className={styles['base-block-body']}>
<WrappedComponent {...this.props} />
</div>
</div>
</div>
)
}
}
}
export default BaseBlock
This is the WrappedComponent:
const HighlightsBlock = (props) => {
return <ListsComponent items={props.items} />
}
export default BaseBlock(HighlightsBlock)
And this is the ListsComponent
const ListsComponent = (props) => {
if (props.items) {
return (
<ul className={styles['styled-list']}>
{props.items.map((item, idx) => {
return (
<li key={idx} className={styles['styled-list-item']}>{item}</li>
)
})}
</ul>
)
} else return (
<h3>No highlights</h3>
)
}
export default ListsComponent
And this is how I'm using the component in my app:
<HighlightsBlock items={this.getHighlights()} title='Highlights' />
Now, I can see the HighlightsBlock component receiving props twice (Once when I'm using it in my App with props, and once inside the HOC Baseblock as WrappedComponent ). If I remove props from either of these places it stops working. I don't understand how this is working.
When you render <HighlightsBlock items={this.getHighlights()} title='Highlights' /> you are actually rendering the component returned by HOC which in turn renders your actually HighlightsBlock component as <WrappedComponent {...this.props} />
You can think of HighlightsBlock component to be nested two level deep and hence you need to pass on the props to it, firstly as {...this.props} from within HOC and then receive it as props in functional component
This is because of this.getHighlights() in this line,
<HighlightsBlock items={this.getHighlights()} title='Highlights' />
Every time you pass props to child component this function is getting executed.
To solve this issue, maintain a state value in your parent component and set that value in getHighlights function like,
getHighlights(){
//you logic to get data
this.setState({items:data.items}); //considering `data` is object which has `items`
}
Now you can pass items like,
<HighlightsBlock items={this.state.items} title='Highlights' />

Categories