I am working with react and react-bootstrap and react-select.
I have one issue, when I clicked the button its like not showing on console and not do any action.
The Input.js functional component is a child of Hero.js functional component.
So the hero.js is the parent component and Input.js is a child component.
And my button is in Parent component(Hero.js).
So I am using the forwarding ref to parent to child component on my button handler.
I am using react-select input in Input.js and I want to focus when button is clicked.
The console is also not showing on button clicked.
here is Hero.js (Parent component)
import React, { useState} from "react";
import "../App.css";
import Input from "./Input";
import Btnfill from "./Btnfill";
function Hero() {
const [Infocus, setInfocus] = useState();
const focusInput = () => {
console.log(Infocus.current);
Infocus.focus();
}
return (
<>
<Input
icon={Search}
cname={{ iconavailable: "input", noIcon: "input-notIcon" }}
placeholder="Search Industries"
ref={setInfocus}
/>
<Btnfill text="Search" size="larger" onClick={focusInput} />
</>
);
}
export default Hero;
Here is Input.js
import React, { useState, useEffect} from "react";
import Select from "react-select";
const Input = React.forwardRef((props, refrence) => {
// Input select options
const options = [
{ value: "chocolate", label: "Chocolate" },
{ value: "strawberry", label: "Strawberry" },
{ value: "vanilla", label: "Vanilla" },
];
return (
<div>
<Select
defaultValue={selectedOption}
onChange={onChangeEvent}
options={options}
className={props.icon ? props.cname.iconavailable : props.cname.noIcon}
placeholder={props.placeholder}
ref={refrence}
/>
</div>
);
})
export default Input;
You want to use ref, but what you actually do in your Hero.js is - you are defining state and pass down the set-state function down.
What you instead need to do:
Define new ref in Hero.jsx: const inputRef = useRef();
In your on button click handler set the focus. To do so, you need to access the current property of the ref: const focusInput = () => inputRef.current.focus();
Pass down ref instead: <Input ... ref={inputRef} />
https://codesandbox.io/s/nervous-lewin-8510tf
Related
I am new to react and come from a background of functional component only.
In my react project,
When I conditionally rendering , ie from false to true, the data inside child component will be gone.
Then I wonder why is that.
Then I heard a concept called unmounting. It means, when my condition change from true to false, the component will get unmounting. And in unmounting, the state inside will gone.
But then, it doesn't add up.
Q: Whenever we re-render any other components, just like the normal situation, we will also unmount component in order to do re-rendering. And our state value would not be gone.
Why this problem was happened especially on having conditional statement in react?
Edit:
My emphsis is not on how to avoid state loss. My question is that why data will be gone in conditional rendering. And why unmounts will cause such problem, but re rendering would not cause such ( both also involves unmounting)
Here is my code
In parent:
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import Child1 from "./child";
import "./styles.css";
function Parent() {
const [message, setMessage] = useState("initial text");
const [showChild,setShowChild] = useState(true);
useEffect(() => {
console.log("useeffect in parent");
});
return (
<div className="App">
<button onClick={() => setShowChild(!showChild)}>show child</button>
{showChild?
<Child1 />
:
null
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Parent />, rootElement);
In child:
import React, { useEffect, useState } from "react";
function Child1() {
useEffect(() => {
console.log("useeffect in child");
console.log("newMessage: " + newMessage);
});
const [newMessage, setNewMessage] = useState("");
return (
<div>
<input onChange={(event) => setNewMessage(event.target.value)} />
</div>
);
}
export default Child1;
Add some picture to illurste what I mean by data lose in conidtional rendering
enter
https://i.stack.imgur.com/UrIhT.png
click to not show it
https://i.stack.imgur.com/0OC87.png
click to show again
https://i.stack.imgur.com/4zlWk.png
Try moving all state management to the parent component and leave the child component 'dumb'. You can pass the setMessage and any other state variables to the child as props.
Parent:
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import Child1 from "./child";
import "./styles.css";
function Parent() {
const [message, setMessage] = useState("initial text");
const [showChild,setShowChild] = useState(true);
useEffect(() => {
console.log("useeffect in parent");
});
return (
<div className="App">
<button onClick={() => setShowChild(!showChild)}>show child</button>
{showChild?
<Child1 setMessage={setMessage}/>
:
null
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Parent />, rootElement);
Child:
import React from "react";
function Child1({setMessage}) {
return (
<div>
<input onChange={(event) => setMessage(event.target.value)} />
</div>
);
}
export default Child1;
The answer for your question is very simple, While unmounting you are removing the component itself from react-dom. The state, props and all the data's handled inside your component will be active only If the component is inside the react-dom. If the particular component is unmounted, all the states and props that was created and processed will also be removed from react-dom along with the component. And the fresh component, state and props will be mounted in the react-dom if you conditionally render the component again.
I'm trying to add simple custom validation (non-empty or minimum characters) to a MUI TextField that is part of React functional component using the Formik useField hook. This reusable component will be a part of Formik form in it's parent component. The use of this functional child component is for it to be a reusable component and so the validation for the Textfield should occur within the component instead of the ValidationScheme attribute in the parent component's Formik tag since the form is not predefined. How can I add this custom validation to the TextField within the child component?
import React from 'react';
import { useField } from "formik";
import { TextField, TextFieldProps } from '#material-ui/core';
type FormikTextFieldProps = {
formikKey: string,
} & TextFieldProps
const FormikTextField = ({ formikKey, ...props }: FormikTextFieldProps) => {
const [field, meta, helpers] = useField(formikKey);
const validate = () => {
if(!field.value){
helpers.setError('The field is empty')
}
}
return (
<>
<TextField
id={field.name}
name={field.name}
helperText={meta.touched ? meta.error : ""}
error={meta.touched && Boolean(meta.error)}
value={field.value}
onChange={field.onChange}
{...props}
/>
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</>
)
}
export default FormikTextField
What I want to do is to create a reusable and convenient way of showing an alert or a confirmation modal.
Using library modals usually require you to import a Modal component and create a state variable and pass it as a prop to the imported component to control its visibility.
What I want to do is to create a custom hook that exports a modal component with all the customization (maybe a wrapper around a Modal component from a library) and a function to toggle the visibility.
Something like below.
This is the hook code:
import React, {useState} from 'react'
import 'antd/dist/antd.css'
import {Modal as AntdModal} from 'antd'
const useModal = () => {
const [on, setOn] = useState(false)
const toggleModal = () => setOn(!on)
const Modal = ({onOK, ...rest}) => (
<AntdModal
{...rest}
visible={on}
onOk={() => {
onOK && onOK()
toggleModal()
}}
onCancel={toggleModal}
/>
)
return {
on,
toggleModal,
Modal,
}
}
export default useModal
And this is how I use it:
import React, {useState} from 'react'
import ReactDOM from 'react-dom'
import useModal from './useModal'
import {Button} from 'antd'
const App = () => {
const {toggleModal, Modal} = useModal()
return (
<div>
<Button type="primary" onClick={toggleModal}>
Open Modal
</Button>
<Modal title="Simple" onOK={() => alert('Something is not OK :(')}>
<p>Modal content...</p>
</Modal>
</div>
)
}
const rootElement = document.getElementById('root')
ReactDOM.render(<App />, rootElement)
Here is a sandbox to see it in action and test it out. There are two buttons, one which shows a Modal which is normally imported from the library (here antd) and one that is from a custom hook useModal.
The one form the hook works except it seems something is wrong with it. The appearing transition is working but when you close the modal it suddenly disappears with no transition. It seems the component is immediately destroyed before transitioning out. What am I doing wrong?
If I understand it correct, you want to render a Component and also need a function which can control it (toggle it's visibility).
Though it is not possible the way you are trying to achieve with the react hooks, because on state change you are actually updating your Modal too and that is causing an unmount of the Dialogue from DOM.
You can use below solution to achieve the same result. The Solution uses a component with forwardRef and useImperativeHandle and will achieve a decoupled function which you can use to toggle your dialogue using button click:
NOTE: You need to upgrade to react and react-dom from v-16.7.0-alpha (as in your sandbox code) to latest (16.14.0) [I have not tried other intermediate versions]
Modal Component:
import React, {useState, forwardRef, useImperativeHandle} from 'react'
import 'antd/dist/antd.css'
import {Modal as AntdModal} from 'antd'
const Modal = forwardRef(({onOK, ...rest}, ref) => {
useImperativeHandle(ref, () => ({
toggleModal: toggleModal
}));
const [on, setOn] = useState(false)
const toggleModal = () => setOn(!on)
return (
<AntdModal
{...rest}
visible={on}
onOk={() => {
onOK && onOK()
toggleModal()
}}
onCancel={toggleModal}
/>
)
});
export default Modal;
And this is how to use it:
import React, {useState, useRef} from 'react'
import ReactDOM from 'react-dom'
import Modal from './ModalWrapper'
import {Button, Modal as AntdModal} from 'antd'
const App = () => {
const [on, setOn] = useState(false)
const toggle = () => setOn(!on)
const modalRef = useRef()
return (
<div>
<Button type="warning" onClick={() => setOn(true)}>
Normal Import
</Button>
<br />
<br />
<Button type="primary" onClick={() => modalRef.current.toggleModal()}>
From Modal Component
</Button>
<AntdModal visible={on} onOk={toggle} onCancel={toggle}>
<p>I was imported directly...</p>
<p>I was imported directly...</p>
<p>I was imported directly...</p>
</AntdModal>
<Modal
title="Simple"
ref={modalRef}
onOK={() => alert('Things are now OK :)')}
>
<p>I was imported from Modal Component...</p>
<p>I was imported from Modal Component...</p>
<p>I was imported from Modal Component...</p>
</Modal>
</div>
)
}
const rootElement = document.getElementById('root')
ReactDOM.render(<App />, rootElement)
I hope it will help your use case.
Thanks.
I can't seem to resolve an infinite loop issue in my react project.
I'm working on a daily-log react app. Let me explain the project briefly. Here is the picture of the code for quick view:
The same code is available at the bottom.
The structure (from top to bottom)
The DailyLog component has a form that uses Question components into which props are passed.
The Question component uses the props to display a question and description. It also contains an Input component into which props are further passed down.
The Input component takes the props and renders the appropriate form input field.
The logic (from bottom to top)
The Input component handles it's own inputState. The state is changed when the user inputs something and the onChangeHandler is triggered.
The Input component also has a useEffect() hook that calls an onInput() function that was passed down as props from DailyLog.
The onInputHandler() in the DailyLog component updates the formState which is the form-wide state containing all input field values. The formState is amended depending on which input field is filled at the time.
The onInputHandler() uses the useCallback() hook which is supposed to stop an infinite loop caused by any parent/child re-renders. But it doesn't work :frowning:
What's wrong in the code? What am I missing here? Code provided below:
//DailyLog.js
import React, { useState, useCallback } from 'react';
import Question from '../components/FormElements/Question';
import questionData from '../components/DailyLog/questionData';
import './DailyLog.css';
const DailyLog = () => {
const [formState, setFormState] = useState();
const onInputHandler = useCallback(
(inputId, inputValue) => {
setFormState({
...formState,
[inputId]: inputValue,
});
},
[formState]
);
return (
<main className="container">
<form action="" className="form">
<Question
id="title"
element="input"
type="text"
placeholder="Day, date, calendar scheme"
onInput={onInputHandler}
/>
<Question
id="focus"
question={questionData.focusQuestion}
description={questionData.focusDescription}
element="textarea"
placeholder="This month's focus is... This week's focus is..."
onInput={onInputHandler}
/>
</form>
</main>
);
};
export default DailyLog;
//Question.js
import React from 'react';
import Input from './Input';
import './Question.css';
const Question = props => {
return (
<div className="form__group">
{props.question && (
<label className="form__label">
<h2>{props.question}</h2>
</label>
)}
<small className="form__description">{props.description}</small>
<Input
id={props.id}
element={props.element}
type={props.type}
placeholder={props.placeholder}
onInput={props.onInput}
/>
</div>
);
};
export default Question;
//Input.js
import React, { useState, useEffect } from 'react';
import './Input.css';
const Input = props => {
const [inputState, setInputState] = useState();
const { id, onInput } = props;
useEffect(() => {
onInput(id, inputState);
}, [id, onInput, inputState]);
const onChangeHandler = event => {
setInputState(event.target.value);
};
// check if question element type is for input or textarea
const element =
props.element === 'input' ? (
<input
id={props.id}
className="form__field"
type={props.type}
value={inputState}
placeholder={props.placeholder}
onChange={onChangeHandler}
/>
) : (
<textarea
id={props.id}
className="form__field"
rows="1"
value={inputState}
placeholder={props.placeholder}
onChange={onChangeHandler}
/>
);
return <>{element}</>;
};
export default Input;
Remove id and onInput from useEffect sensivity list
useEffect(() => {
onInput(id, inputState);
}, [inputState]);
And set default value of inputState to '' as follow:
const [inputState, setInputState] = useState('');
To prevent 'A component is changing an uncontrolled input of type text to be controlled error in ReactJS'. Also you can init formState:
const [formState, setFormState] = useState({title:'', focus:''});
I'm using the React Context API with the main intent of avoiding prop drilling. Right now my Context includes a useState and various functions that update the state - these are put into a const object that is passed as the value prop of ActionsContext.Provider. This is an abstraction of my current component hierarchy:
Header
---NavPanel
ContentContainer
---Content (Context.Consumer being returned in this component)
where Header and ContentContainer are sibling elements and NavPanel and ContentContainer are their respective children.
I initially put the Context.Consumer in Content because the other elements did not need it. However I'm building a feature now where NavPanel needs to know about the state that's managed by the Context. So I put another Consumer in NavPanel, only to find that a separate Consumer means a separate instance of the state.
Is there any smart workaround that gives NavPanel and Content access to the same state, that doesn't involve putting the Consumer in the parent component of Header and Content? That would result in a lot of prop drilling with the way my app is currently structured.
Codesandbox example of multiple instances: https://codesandbox.io/s/context-multiple-consumers-v2wte
Several things:
You should have only one provider for every state you want to share.
<ContextProvider>
<PartOne />
<hr />
<PartTwo />
</ContextProvider>
It is better to split your context in several contexts so you pass values instead of objects. This way when you update your state React will detect it is different instead of comparing the same object.
Your input should be a controlled component https://reactjs.org/docs/forms.html
Consider using the useContext API for better ergonomics if you are using React 16.8 instead of ContextConsumer.
With these changes, your code would be:
MyContext.js
import React, { useState } from "react";
export const MyItemContext = React.createContext();
export const MySetItemContext = React.createContext();
export const MyHandleKeyContext = React.createContext();
const ContextProvider = props => {
const [itemBeingEdited, setItemBeingEdited] = useState("");
const handleKey = event => {
if (event.key === "Enter") {
setItemBeingEdited("skittles");
} else if (event.key === "K") {
setItemBeingEdited("kilimanjaro");
} else {
setItemBeingEdited("");
}
};
const editFunctions = {
itemBeingEdited,
setItemBeingEdited,
handleKey
};
return (
<MyItemContext.Provider value={itemBeingEdited}>
<MyHandleKeyContext.Provider value={handleKey}>
<MySetItemContext.Provider value={setItemBeingEdited}>
{props.children}
</MySetItemContext.Provider>
</MyHandleKeyContext.Provider>
</MyItemContext.Provider>
);
};
export default ContextProvider;
PartOne.js
import React, { useContext } from "react";
import ContextProvider, {
MyContext,
MyItemContext,
MySetItemContext,
MyHandleKeyContext
} from "./MyContext";
const PartOne = () => {
// blah
const itemBeingEdited = useContext(MyItemContext);
const handleKey = useContext(MyHandleKeyContext);
const setItem = useContext(MySetItemContext);
return (
<React.Fragment>
<span>{itemBeingEdited}</span>
<input
placeholder="Type in me"
onKeyDown={handleKey}
value={itemBeingEdited}
onChange={e => setItem(e.target.value)}
/>
</React.Fragment>
);
};
export default PartOne;
PartTwo.js
import React, { useContext } from "react";
import ContextProvider, {
MyContext,
MyItemContext,
MySetItemContext,
MyHandleKeyContext
} from "./MyContext";
const PartTwo = () => {
// blah
const itemBeingEdited = useContext(MyItemContext);
const handleKey = useContext(MyHandleKeyContext);
const setItem = useContext(MySetItemContext);
return (
<React.Fragment>
<span>{itemBeingEdited}</span>
<input
value={itemBeingEdited}
type="text"
placeholder="Type in me"
onChange={e => setItem(e.target.value)}
onKeyDown={handleKey}
/>
</React.Fragment>
);
};
export default PartTwo;
index.js
import React from "react";
import ReactDOM from "react-dom";
import PartOne from "./PartOne";
import PartTwo from "./PartTwo";
import ContextProvider from "./MyContext";
import "./styles.css";
function App() {
return (
<div className="App">
<ContextProvider>
<PartOne />
<hr />
<PartTwo />
</ContextProvider>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
CodeSandbox: https://codesandbox.io/s/context-multiple-consumers-vb9oj?fontsize=14