I have been trying to come up with a custom hook to make the textfield configurable, i.e pass the set of data to a custom hook which would give me the text field that needs to be used.
The text field using the hook is being rendered as expected but I do not understand why this approach is breaking the input created using the custom hook. After every keystroke the input is losing focus and is not working as the other input that is using useState directly. It would be great if someone can explain what is going wrong and what I failed to understand.
App.js
import React, { useState } from "react";
import ReactDOM from "react-dom";
import useTextFieldBroken from "./useTextFieldBroken";
import "./styles.css";
function App() {
const [notBrokenValue, notBrokenSetValue] = useState("");
const [TextFieldBrokenInputOne] = useTextFieldBroken(
"brokenOne",
"Broken Input One",
""
);
const notBrokenOnChange = e => {
notBrokenSetValue(e.target.value);
};
return (
<div>
<label htmlFor="notBroken">
<h3>Not Broken Input</h3>
<input
id="notBroken"
onChange={notBrokenOnChange}
value={notBrokenValue}
/>
</label>
<TextFieldBrokenInputOne />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
customHook.js
import React, { useState } from "react";
const useTextFieldBroken = (id, label, initialValue = "") => {
const [value, setValue] = useState(initialValue);
const handleChange = e => {
setValue(e.target.value);
};
const TextField = () => {
console.log("Rendered the input field");
return (
<label htmlFor={id}>
<h3>{label}</h3>
<input
type="text"
name={id}
id={id}
onChange={handleChange}
value={value}
/>
</label>
);
};
return [TextField, value, setValue];
};
export default useTextFieldBroken;
https://codesandbox.io/s/4xj382vj40
Your input is losing focus because you're completely re-rendering the tree that creates it on each change.
The good news is that you don't need a hook to do this, just convert your hook into a functional component instead:
App.js
import React, { useState } from "react";
import ReactDOM from "react-dom";
import TextFieldBroken from "./useTextFieldBroken";
import "./styles.css";
function App() {
const [notBrokenValue, notBrokenSetValue] = useState("");
const notBrokenOnChange = e => {
notBrokenSetValue(e.target.value);
};
return (
<div>
<label htmlFor="notBroken">
<h3>Not Broken Input</h3>
<input
id="notBroken"
onChange={notBrokenOnChange}
value={notBrokenValue}
/>
</label>
<TextFieldBroken label="Previously Broken" id="previously-broken" />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
customHook.js
import React, { useState } from "react";
const TextFieldBroken = ({ id, label, initialValue = "" }) => {
const [value, setValue] = useState(initialValue);
const handleChange = e => {
setValue(e.target.value);
};
return (
<label htmlFor={id}>
<h3>{label}</h3>
<input
type="text"
name={id}
id={id}
onChange={handleChange}
value={value}
/>
</label>
);
};
export default TextFieldBroken;
Related
Description of the problem
I'm trying to display different tasks from my context. Every time I submit my form, I want a new Task to appear under the other, instead of that after form submission the old task just gets replaced by the new one. I'm sure it's a mediocre problem, but this is my first react project.
Also, I'm using styled components so I'm leaving the css out of the code blocks for readability.
Context
import { createContext, useState } from "react";
export const TaskContext = createContext();
export const TaskProvider = ({ children }) => {
const [tasks, setTasks] = useState([]);
return (
<TaskContext.Provider
value={{
tasks,
setTasks,
}}
>
{children}
</TaskContext.Provider>
);
};
Form
import React from "react";
import styled from "styled-components";
import { useState, useContext } from "react";
import { TaskContext, TaskProvider } from "../TaskContext";
export default function Form() {
const [text, setText] = useState([]);
const [time, setTime] = useState([]);
const [reminder, setReminder] = useState(false);
const task = useContext(TaskContext);
const submitHandler = (e) => {
e.preventDefault();
task.setTasks([{ text, time, reminder }]);
setText("");
setTime("");
setReminder(false);
};
return (
<StyledForm onSubmit={submitHandler}>
<FormControl>
<Label>Task</Label>
<Input
type="text"
placeholder="Add Task"
value={text}
required
onChange={(e) => setText(e.target.value)}
></Input>
</FormControl>
<FormControl>
<Label>Day & Time</Label>
<Input
type="text"
placeholder="Add Day ß Time"
required
value={time}
onChange={(e) => setTime(e.target.value)}
></Input>
</FormControl>
<FormControlCheck>
<FormControlCheckLabel>Set Reminder</FormControlCheckLabel>
<FormControlCheckInput
type="checkbox"
value={reminder}
onChange={(e) => setReminder(e.currentTarget.checked)}
></FormControlCheckInput>
</FormControlCheck>
<Submit type="submit" value="Save Task"></Submit>
</StyledForm>
);
}
Tasks
import React from "react";
import { useContext } from "react";
import { TaskContext } from "../TaskContext";
import Task from "./Task";
export default function Tasks() {
const context = useContext(TaskContext);
return (
<>
{context.tasks.map((task) => (
<Task text={task.text} time={task.time} reminder={task.reminder}></Task>
))}
</>
);
}
Task
import React from "react";
import styled from "styled-components";
export default function Task(props) {
return (
<StyledTask>
<HeaderThree>{props.text}</HeaderThree>
<Parag>{props.time}</Parag>
</StyledTask>
);
}
Thanks if you took the time to read my post!
You just put a new task object into task array, that's why all old tasks will disappear. The solutions is you spread out the old tasks first, then add new task after that.
in submitHandler
replace task.setTasks([{ text, time, reminder }]);
with task.setTasks([...task.tasks, { text, time, reminder }]);
I am trying to call useEffect funtion onchange of local variable, but its not working is only works if i use it with useState variable, I know there might be some basic thing here that I am not aware of.
sandbox link: https://codesandbox.io/s/affectionate-gareth-igyv7?file=/src/demo.js
import React, { useEffect, useState } from "react";
import "./styles.css";
export default function Demo() {
const [value, setValue] = useState("");
let valueOne, valueTwo;
const setValueOne = (value) => {
valueOne = value;
};
useEffect(() => {
console.log(value);
console.log(valueOne);
}, [value, valueOne]);
return (
<div>
<h1>Demo</h1>
<input
placeholder="useState"
onChange={(e) => setValue(e.target.value)}
/>
<input
placeholder="function"
onChange={(e) => setValueOne(e.target.value)}
/>
{/* {console.log(valueOne)} */}
</div>
);
}
setValueOne will not rerender your component, If you want to fire a re-render, useEffect function needs to have a useState which basically hold state between re-renders.
You can try managing your state like below, its more readable and it will work too.
import React, { useState } from "react";
import "./styles.css";
export default function Demo() {
const [valueOne, setValueOne] = useState("");
const [valueTwo, setValueTwo] = useState("");
const handleValueOne = (e) => {
setValueOne(e.target.value);
};
const handleValueTwo = (e) => {
setValueTwo(e.target.value);
};
return (
<div>
<h1>Demo</h1>
<input
value={valueOne}
placeholder="useState"
onChange={handleValueOne}
/>
<input
value={valueTwo}
placeholder="function"
onChange={handleValueTwo}
/>
{/* {console.log(valueOne)} */}
</div>
);
}
i am new to react, i want to call the state of an outside function, for example :
export default function Child() {
const [succeeded, setSucceeded] = useState(false);
}
export default function Parent() {
if(Child.succeeded){
// do the following
}
}
i know that props are used for const objects only, and i don't want to merge both functions in a signle one to keep things organised, i would like to check for child's state to do the next step, or to callback the parent function with the new state to notify it. is there any way to do it ? Thanks a lot for your time.
Another approach is that you can use the useRef, which is very handy in some cases.
import React, {useState} from "react";
export default function Child({nameRef}) {
const [name, setName] = useState('');
React.useEffect(() => {
nameRef.current = name;
}, [name]);
return (
<>
<input nameRef={nameRef} type="text" onChange={event => setName(event.target.value)} />
</>
);
}
import React, { useState, useRef } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import Child from './Child';
function App() {
let [name, setName] = useState("Nate");
let nameRef = useRef();
const submitButton = () => {
console.log(nameRef.current);
};
return (
<div className="App">
<p>{name}</p>
<div>
<Child nameRef={nameRef} />
<button type="button" onClick={submitButton}>
Submit
</button>
</div>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
For some reason, the showNumber variable gets updated upon submit click, but not in in return statement. Anyhow, is there a better way to write this? Keep in mind the line in should not simply change with every input onChange but rather, only after a submit button click. I suppose one solution would be to select input via refs, but I'm hoping there's a better way. Please help.
function App() {
const [number, setNumber] = useState(1);
let showNumber = 0;
const rerender = () => {
showNumber = number;
}
const handleChange = (e) => { setNumber(e.target.value) }
return (
<header>
<input type='number' onChange={handleChange} />
<button type='submit' onClick={rerender}>submit</button>
<p>the number is {showNumber}</p>
</header>
);
}
export default App;
By Wrapping the form tag you can achieve, so when you click on the submit only it will get the form values and you can fetch the values by using name attribute
Sample Code
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
function App() {
const [number, setNumber] = useState(1);
const rerender = e => {
e.preventDefault();
const data = new FormData(e.target);
setNumber(data.get("number"));
};
return (
<header>
<form onSubmit={rerender}>
<input type="number" name="number" />
<button>Submit</button>
</form>
<p>the number is {number}</p>
</header>
);
}
export default App;
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Working codesandbox
I believe it is because it binds the value back to the property inputText but just want to make sure I'm stating this correctly.
import React, { useState } from "react";
const InputElement = () => {
const [inputText, setInputText] = useState("");
return (
<div>
<input
placeholder="Enter Some Text"
onChange={e => {
setInputText(e.target.value);
}}
/>
</div>
);
};
export default InputElement;
This is a good example of 2 way data binding because when you update the state, the UI changes, and when the UI changes, the state changes. Just need to remind you to set the value prop on the <input> element to inputText so that it's a controlled component.
import React, { useState } from "react";
const InputElement = () => {
const [inputText, setInputText] = useState("");
return (
<div>
<input
placeholder="Enter Some Text"
onChange={e => {
setInputText(e.target.value);
}}
value={inputText}
/>
</div>
);
};
export default InputElement;