(in REACT)
i have app function :
function App() {
const [welcomeMenu, setWelcomeMenu] = useState(true);
const [gameMenu, setGameMenu] = useState(false);
const [username, setUsername] = useState('');
const welcomeMenuShow = () => {
setWelcomeMenu(false);
}
const getUserName = (value) => {
setUsername(value);
console.log(username);
};
return (
<div className="App">
{
welcomeMenu ? <WelcomeMenu gameStarter={welcomeMenuShow} getUserName={getUserName}/> : null
}
</div>
);
}
in welcomemenu component i pass getUserName function to get username which user input
next in Welcome menu i have :
const WelcomeMenu = ({ gameStarter, getUserName }) => {
return (
<div className="welcome-menu">
<WelcomeText />
<WelcomeBoard gameStarter={gameStarter} getUserName={getUserName}/>
</div>
)
};
i pass get User Name in second time
in WelcomeBoard i have:
const WelcomeBoard = ({ gameStarter, getUserName }) => {
const [text, setText] = useState('');
const [warning, setWarning] = useState(false);
const checkBtn = (event) => {
if(text) {
gameStarter();
} else {
setWarning(true);
setTimeout(() => {
setWarning(false);
}, 3000);
}
};
const handleChange = (event) => {
setText(event.target.value);
};
return (
<div className="welcome-board">
<div className="username">Please enter the name</div>
<input type="text" value={text} onChange={handleChange} className="username-input" />
<button className="username-btn" onClick={() => {
getUserName(text);
checkBtn();
}}>start</button>
{warning ? <Warning /> : null}
</div>
)
};
in input onchange i make state and pass the input value on text state
next on button i have on click which active 2 function:
getUserName(text) // text is a state text with input value
checkBtn()
and after a click button in app i activate getUserName(text), this function pass the text in username state and here is a problem
when i try to see this text console.log(username) - it's give me null
but it if i try to see value console.log(value) - i see my input text
i don't understand how to fix that
react setState is async, which means those state variables are updated in the NEXT RENDER CYCLE(think of it as a thread or buffer).
try running this code if you want to understand what is happening BEHIND THE SCENES.
let renderCount = 0;
function TestApp() {
renderCount++;
const [state, setState] = useState(0);
const someRef = useRef(0);
someRef.current = state;
const someCallback = () => {
const someValue = new Date().getTime();
setState(someValue);
console.log(someRef.current, renderCount);
setTimeout(() => {
console.log(someRef.current, renderCount);
},100)
}
return <button onClick={someCallback}>clickme<button>;
}
Related
I am just trying to figure out how to do my to-do list and currently am just experimenting with adding elements containing text given a text input element.
The issue is presented in this clip: https://imgur.com/a/DDTyv1I
import { useState } from "react"
function App() {
const [inputVal, setInputVal] = useState('')
const [tasks, setTasks] = useState([])
console.log(inputVal);
return <>
<Input valor = {setInputVal} inputVal={inputVal} tasks={tasks} setTasks={setTasks}/>
{
tasks.map(e=>(
<Display text={e.text}/>
))
}
</>
}
const Input = ({valor, inputVal, tasks, setTasks}) =>{
const keyPressed = (val) =>{
if(val.key === 'Enter'){
valor(val.target.value)
setTasks([
...tasks, {text: inputVal, key: Math.random()*2000}
])
}
}
return <>
<input type="text" onKeyUp={keyPressed}/>
</>
}
const Display = ({text}) => {
return <>
<h1>{text}</h1>
</>
}
export default App;
I believe this is happening because you are not using onChange on your input so your state is going stale and you are always one value behind.
I have tidied up the code and added some missing pieces (like the value attribute in the input element).Then I split the function that takes care of the submission to 2 functions - one function that is handling changing the input value and one that submits the value as a new entry to your tasks list
import { useState } from "react"
const Input = ({ input, handleChange, tasks, setTasks }) => {
const onSubmit = (e) => {
if (e.key === 'Enter') {
setTasks([
...tasks,
{ text: input, key: Math.random()*2000 }
])
setInput('');
}
}
const handleChange = (e) => {
setInput(e.target.value)
}
return <input type="text" onKeyUp={onSubmit} onChange={handleChange} value={input}/>
}
const Display = ({ text }) => <h1>{text}</h1>
const App = () => {
const [input, setInput] = useState('')
const [tasks, setTasks] = useState([])
return <>
<Input input={input} setInput={setInput} tasks={tasks} setTasks={setTasks}/>
{tasks.map((task) => (
<Display text={task.text}/>
))
}
</>
}
When keyPressed is called, your code calls setInputVal (valor) and setTasks. The setTask is being called before setInputVal actually has time to update the state, so it sets the ”old” value. This is because state setting is asynchronous and the code does not wait for the inputVal to be set before setting the task.
What I would like to happen is when displayBtn() is clicked for the items in localStorage to display.
In useEffect() there is localStorage.setItem("localValue", JSON.stringify(myLeads)) MyLeads is an array which holds leads const const [myLeads, setMyLeads] = useState([]); myLeads state is changed when the saveBtn() is clicked setMyLeads((prev) => [...prev, leadValue.inputVal]);
In DevTools > Applications, localStorage is being updated but when the page is refreshed localStorage is empty []. How do you make localStorage persist state after refresh? I came across this article and have applied the logic but it hasn't solved the issue. I know it is something I have done incorrectly.
import List from './components/List'
import { SaveBtn } from './components/Buttons';
function App() {
const [myLeads, setMyLeads] = useState([]);
const [leadValue, setLeadValue] = useState({
inputVal: "",
});
const [display, setDisplay] = useState(false);
const handleChange = (event) => {
const { name, value } = event.target;
setLeadValue((prev) => {
return {
...prev,
[name]: value,
};
});
};
const localStoredValue = JSON.parse(localStorage.getItem("localValue")) ;
const [localItems] = useState(localStoredValue || []);
useEffect(() => {
localStorage.setItem("localValue", JSON.stringify(myLeads));
}, [myLeads]);
const saveBtn = () => {
setMyLeads((prev) => [...prev, leadValue.inputVal]);
// setLocalItems((prevItems) => [...prevItems, leadValue.inputVal]);
setDisplay(false);
};
const displayBtn = () => {
setDisplay(true);
};
const displayLocalItems = localItems.map((item) => {
return <List key={item} val={item} />;
});
return (
<main>
<input
name="inputVal"
value={leadValue.inputVal}
type="text"
onChange={handleChange}
required
/>
<SaveBtn saveBtn={saveBtn} />
<button onClick={displayBtn}>Display Leads</button>
{display && <ul>{displayLocalItems}</ul>}
</main>
);
}
export default App;```
You've fallen into a classic React Hooks trap - because using useState() is so easy, you're actually overusing it.
If localStorage is your storage mechanism, then you don't need useState() for that AT ALL. You'll end up having a fight at some point between your two sources about what is "the right state".
All you need for your use-case is something to hold the text that feeds your controlled input component (I've called it leadText), and something to hold your display boolean:
const [leadText, setLeadText] = useState('')
const [display, setDisplay] = useState(false)
const localStoredValues = JSON.parse(window.localStorage.getItem('localValue') || '[]')
const handleChange = (event) => {
const { name, value } = event.target
setLeadText(value)
}
const saveBtn = () => {
const updatedArray = [...localStoredValues, leadText]
localStorage.setItem('localValue', JSON.stringify(updatedArray))
setDisplay(false)
}
const displayBtn = () => {
setDisplay(true)
}
const displayLocalItems = localStoredValues.map((item) => {
return <li key={item}>{item}</li>
})
return (
<main>
<input name="inputVal" value={leadText} type="text" onChange={handleChange} required />
<button onClick={saveBtn}> Save </button>
<button onClick={displayBtn}>Display Leads</button>
{display && <ul>{displayLocalItems}</ul>}
</main>
)
I would like to display API data on a button click. My state is set to an empty array initially and I guess that's why it shows an empty array at first click, but what could I write differently to show data on first click ? After the initial click it works.
My code:
const Search = () => {
const [textInput, setTextInput] = useState('');
const [tickers, setTickers] = useState([]);
const [prices, setPrices] = useState([]);
const [isClicked, setIsClicked] = useState(false);
const inputHandler = (e) => {
setTextInput(e.target.value);
}
const showData = async (e) => {
e.preventDefault();
const url = `https://www.alphavantage.co/query?function=TIME_SERIES_MONTHLY&symbol=${textInput}&apikey=${process.env.REACT_APP_ALPHA_VANTAGE_API_KEY}`
try {
const data = await axios.get(url);
if(data) {
setPrices([data.data['Monthly Time Series']['2021-11-30']]);
}
} catch(err) {
console.log(err)
}
console.log(prices);
setIsClicked(true);
setTextInput('');
}
return (
<StyledSearch>
<h1>Security Price Monitor App </h1>
<form onSubmit={submitSearch}>
<input type="text" value={textInput} onChange={inputHandler} placeholder='Enter Ticker'/>
<button type="submit" onClick={showData}>Search</button>
</form>
{isClicked &&
<Chart tickers = {tickers} setTickers={setTickers} prices={prices} setPrices={setPrices}/>
}
</StyledSearch>
)
}
Try to change your conditional rendering condition to:
{prices.length > 0 && (
<Chart
tickers={tickers}
setTickers={setTickers}
prices={prices}
setPrices={setPrices}
/>
)}
I think that you can remove the isClicked state. It is redundant.
I'm using react-hook-form library with a multi-step-form
I tried getValues() in useEffect to update a state while changing tab ( without submit ) and it returned {}
useEffect(() => {
return () => {
const values = getValues();
setCount(values.count);
};
}, []);
It worked in next js dev, but returns {} in production
codesandbox Link : https://codesandbox.io/s/quirky-colden-tc5ft?file=/src/App.js
Details:
The form requirement is to switch between tabs and change different parameters
and finally display results in a results tab. user can toggle between any tab and check back result tab anytime.
Implementation Example :
I used context provider and custom hook to wrap setting data state.
const SomeContext = createContext();
const useSome = () => {
return useContext(SomeContext);
};
const SomeProvider = ({ children }) => {
const [count, setCount] = useState(0);
const values = {
setCount,
count
};
return <SomeContext.Provider value={values}>{children}</SomeContext.Provider>;
};
Wrote form component like this ( each tab is a form ) and wrote the logic to update state upon componentWillUnmount.
as i found it working in next dev, i deployed it
const FormComponent = () => {
const { count, setCount } = useSome();
const { register, getValues } = useForm({
defaultValues: { count }
});
useEffect(() => {
return () => {
const values = getValues(); // returns {} in production
setCount(values.count);
};
}, []);
return (
<form>
<input type="number" name={count} ref={register} />
</form>
);
};
const DisplayComponent = () => {
const { count } = useSome();
return <div>{count}</div>;
};
Finally a tab switching component & tab switch logic within ( simplified below )
const App = () => {
const [edit, setEdit] = useState(true);
return (
<SomeProvider>
<div
onClick={() => {
setEdit(!edit);
}}
>
Click to {edit ? "Display" : "Edit"}
</div>
{edit ? <FormComponent /> : <DisplayComponent />}
</SomeProvider>
);
}
I have a custom input component that has an edit mode, I use multiple inputs and my goal is to extract all the state from the input and put it on the parent.
I managed to do it for the input value since I create a useState for every input, but how can I achieve the same thing for the toggle behavior. I want to have a button next to each input that toggles the state of the input.(Show/Hide the input).
Right now when I press edit all the edits Booleans get triggered so all the inputs become visible.
How can I toggle one input at a time from the parent, Basically I want my custom components to be purely for presentation and the state should live on the parent
Example:
const Parent = () => {
const [value1, setValue1] = useState('')
const [value2, setValue2] = useState('')
const [value3, setValue3] = useState('')
const [editMode, setEditMode] = useState(false)
return (
<div>
<ChildCustomInput value={value1} updateValue={setValue1} editMode={editMode} updateEditMode={setEditMode} />
<ChildCustomInput value={value2} updateValue={setValue2} editMode={editMode} updateEditMode={setEditMode}/>
<ChildCustomInput value={value3} updateValue={setValue3} editMode={editMode} updateEditMode={setEditMode}/>
</div>
)
}
const ChildCustomInput = (props)=> {
// ref to update the input's value in the parent
const textInputEl = useRef(null)
// method to update the parent's values
const updateComponentValue = () => {
props.updateValue(textInputEl.current.value)
props.updateEditMode(!props.editMode)
}
// Toggle Edit mode
const changeEditMode = () => {
props.updateEditMode(!props.editMode)
}
const renderEditView = () => {
return (
<div>
<input ref={textInputEl}></input>
<button onClick={updateComponentValue}>
OK
</button>
</div>
)
}
const renderDefaultView = () => {
return (
<div>
<div>
{props.value}
</div>
<button onClick={changeEditMode}>
EDIT
</button>
</div>
)
}
return (
<div>
{
props.editMode
? renderEditView()
: renderDefaultView()
}
</div>
)
}
export default Parent;
It seems that you need a editMode boolean in the parent state for each of your input (ie editMode1, editMode2, editMode3), and each ChildCutomInput should use its own editModeX and setEditModeX instead of sharing the same boolean.
const Parent = () => {
const [value1, setValue1] = useState('');
const [value2, setValue2] = useState('');
const [value3, setValue3] = useState('');
const [editMode1, setEditMode1] = useState(false);
const [editMode2, setEditMode2] = useState(false);
const [editMode3, setEditMode3] = useState(false);
return (
<div>
<ChildCustomInput value={value1} updateValue={setValue1} editMode={editMode1} updateEditMode={setEditMode1} />
<ChildCustomInput value={value2} updateValue={setValue2} editMode={editMode2} updateEditMode={setEditMode2}/>
<ChildCustomInput value={value3} updateValue={setValue3} editMode={editMode3} updateEditMode={setEditMode3}/>
</div>
)
}
For this case you should have a separated state that manages the toggle property for each input. Like:
const [value1EditMode, setValue1EditMode] = useState(false);
const [value2EditMode, setValue2EditMode] = useState(false);
And so on for each input.
But I suggest you to use the useReducer hook, so you just manage the whole child components state with just one reducer. You could do something like this:
const initialState = {
input1: {
value: "",
editMode: false
},
input2: {
value: "",
editMode: false
},
input3: {
value: "",
editMode: false
}
};
function reducer(state, action) {
switch (action.type) {
case "CHANGE_VALUE":
let newState = {
...state
};
newState[action.key].value = action.value;
return newState;
case "SET_EDIT_MODE":
newState = {
...state
};
newState[action.key].editMode = action.editMode;
return newState;
default:
throw new Error();
}
}
const Parent = () => {
const [state, dispatch] = useReducer(reducer, initialState);
function updateValue(key, value) {
dispatch({ type: "CHANGE_VALUE", value, key });
}
function updateEditMode(key, editMode) {
dispatch({ type: "SET_EDIT_MODE", editMode, key });
}
return (
<div>
<ChildCustomInput
value={state.input1.value}
updateValue={value => {
updateValue("input1", value);
}}
editMode={state.input1.editMode}
updateEditMode={editMode => {
updateEditMode("input1", editMode);
}}
/>
<ChildCustomInput
value={state.input2.value}
updateValue={value => {
updateValue("input2", value);
}}
editMode={state.input2.editMode}
updateEditMode={editMode => {
updateEditMode("input2", editMode);
}}
/>
<ChildCustomInput
value={state.input3.value}
updateValue={value => {
updateValue("input3", value);
}}
editMode={state.input3.editMode}
updateEditMode={editMode => {
updateEditMode("input3", editMode);
}}
/>
</div>
);
};
Here is a working example: https://codesandbox.io/s/eloquent-hellman-8on6s?file=/src/Parent.js:52-1818