I have created a reproducible exam of my problem, I don't understand why after the setDefaultValue is called and the component is updated (you can see it's updated using the result of my console.log) If now I click on the reset button instead of the new defaultValue I see the old one.
Here is a link to the example showing this problem, I'll also paste the code here
https://codesandbox.io/s/wonderful-tree-wtsgb4?file=/src/App.js
import "./styles.css";
import {useState, useRef} from 'react';
import TextBox from './TextBox';
export default function App() {
const textboxAPI = useRef(null)
const [defaultValue ,setDefaultValue] = useState('First')
return (
<div className="App">
<div style={{fontWeight: 'bold'}}>To reproduce please first click on the default value button then on the reset button</div>
<TextBox getAPI={(api) => textboxAPI.current = api} defaultValue={defaultValue}/>
<button onClick={() => setDefaultValue('second')}>1- Click me to change default value to "second"</button>
<button onClick={() => textboxAPI.current.reset()}>2- Click me to call reset inside Textbox</button>
</div>
);
}
import {useEffect, useState} from 'react';
const TextBox = ({defaultValue, getAPI}) => {
const [value, setValue] = useState(defaultValue || '')
useEffect(() => {
if (getAPI) {
getAPI({
reset: reset,
})
}
}, [])
const reset = () => {
console.log('TextBox Reset DefaultValue', defaultValue)
setValue(defaultValue)
}
console.log('TextBox DefaultValue', defaultValue)
return <div>{value}</div>
}
export default TextBox;
To reproduce the problem:
1- Click on the first button to set a new defaultValue, see the console.log, you can see the defaultValue has changed inside the TextBox Component
2- Click the reset button, it calls the reset function inside TextBox but the default value logged there has the previous value!
Here you save in textboxAPI.current function reset but just one time after first render of TextBox component. Function reset has a defaultValue in a closure and its value is 'First' during first render. So each next time you call textboxAPI.current.reset(), you call the reset function with defaultValue==='First' in its closure.
But you parent component controls child state and React does not recommend to manage your logic like that.
[UPDATED]
That will fix your issue, but I don not recommend to organize a state logic like that:
const TextBox = ({defaultValue, getAPI}) => {
const [value, setValue] = useState(defaultValue || '')
const reset = () => {
console.log('TextBox Reset DefaultValue', defaultValue)
setValue(defaultValue)
}
if (getAPI) {
getAPI({
reset: reset,
})
}
console.log('TextBox DefaultValue', defaultValue)
return <div>{value}</div>
}
export default TextBox;
Based on what I learned from the comments I tried using Hooks but There were too many changes needed especially I had some issues with React.lazy so I tried to look for more solutions until I found that using a combination of forwardRef and useImperativeHandle I can export my reset function without changing my current structure and it works as it should, I thought that I should share my solution for anyone else who might be looking for an alternative solution to Hooks.
Related
I stored api data to a state and tried to pass it to another component as prop but its behaving differently there.
I'm trying to pass the animeList data to the AnimeCard component but when i start typing in the input it show undefined or previous search result as many times as i press something in the console and on submit it how two array of the value.
const SearchBar = () => {
const [search, setSearch] = useState('')
const [animeList, setAnimeList] = useState()
const animeSearch = async (query) => {
const temp = await fetch(`https://api.jikan.moe/v3/search/anime? q=${query}&order_by=title&sort=asc&limit=10`)
.then(res => res.json())
//console.log(temp.results) it works here
setAnimeList(temp.results)
}
const handleSearch = (e) => {
e.preventDefault()
animeSearch(search)
}
return (
<div className='center'>
<form onSubmit={handleSearch}>
<input placeholder='search' type='search' value={search} onChange={(e) => setSearch(e.target.value)} />
</form>
<AnimeCard animeList={animeList} />
</div>
)
}
export default SearchBar
const AnimeCard = ({animeList}) => {
//trouble here
console.log(animeList)
}
export default AnimeCard
enter image description here
Created an Codesandbox for you.
Here is it
You have many problem with your code.
Why it show undefined many time:
When component(SearchBar) render, your AnimeCard component will render. The time it render, you did not set anything to animeList state.
Why I show many times? React component will re-render if any state change. search is one of your state. everytime you type something. It change which caused re-render.
How to fix? Add a condition to it like what I did in line 31.
Why did it log many times when you get the result:
you have react's StricMode component wrapped in index.js
fix:
Delete <StrictMode> in index.js which cause double render.
you should console.log inside useEffect hook.
ps: advice: dont forget to pass default value for state
It was causing trouble because i didn't set my useState to array,
const [animeList, setAnimeList] = useState([])
that was it
I'm building a basic custom component for input masking (rolling my own!), which returns an input field for a form.
import React, { useState, useEffect, useRef } from "react";
import mask from "./mask";
const InputMask = props => {
let {
field,
register,
type,
inputMaskType,
defaultValue
} = props;
const inputField = useRef();
const [fieldValue, setFieldValue] = useState("");
const onInputLoad = () => {
setFieldValue(mask.maskShortDateOnLoad({
inputField,
defaultValue
}));
};
const onInputChange = () => {
setFieldValue(mask.maskInput({
type: inputMaskType,
inputField,
defaultValue
}));
};
useEffect(() => {
onInputLoad();
}, []);
return (
<>
<input
{...register(field)}
ref={inputField}
type={type}
onChange={onInputChange}
value={fieldValue || ""}
/>
</>
);
};
export default InputMask;
Used like so, in the parent component, in the form:
<InputMask
type="text"
field="from_date"
register={register}
inputMaskType={inputMaskType.SHORT_DATE}
defaultValue={selectedEntity.from_date}
/>
It's binding to the field when the form loads, because the registered field is reading the data in. It shows up. On save, however, the custom field's data is not updated. It remains the same as it was when first loaded.
Looking at it in dev tools, it has the correct "name" property on the tag, and it looks like all the others in the form.
I guess I don't get it! It's an input, nested in another component, and it's registered like every other input in the form. I'm passing register down and doing what I'm supposed to. Is the state change reloading w/ the old data? Am I going to be forced to use a Controller? If so...how in this case? What am I doing wrong?
I have a 3 files:
Main component,
File with states that are stored in local storage
A file with a reset function for resetting these same states to default
values.
I import the file with the states and reset file in the main component and everything is ok. But when I try use reset function for set localState value to default, i got error “Error: Invalid hook call. Interceptors can only be called inside the body of a functional component. "
I read the documentation on react, but I did not understand the error
First file code:
import React from "react";
import { LocalStorage } from "./localState";
import { resetLocalStorage } from "./resetLocalState";
function App() {
const localState = LocalStorage(); // local storage keys
const resetState = () => resetLocalStorage(); // reset local storate states
return (
<div>
<button onClick={() => resetState()}>Refresh State to default</button>
<br />
<button
onClick={() => localState.setLocalStorageState("State was changed")}
>
Change State
</button>
<p>{localState.localStorageState}</p>
</div>
);
}
export default App;
Second file code:
import { useState, useEffect } from "react";
const useLocalStorageList = (key, defaultValue) => {
const stored = localStorage.getItem(key);
const initial = stored ? JSON.parse(stored) : defaultValue;
const [value, setValue] = useState(initial);
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
};
//local storage keys
export const LocalStorage = () => {
const [localStorageState, setLocalStorageState] = useLocalStorageList(
"State",
"Default Value"
);
return { localStorageState, setLocalStorageState };
};
Third file code
import { LocalStorage } from "./localState";
export const resetLocalStorage = () => {
const localState = LocalStorage(); //local storage keys
localState.setLocalStorageState("Default Value");
};
Link to SandBox
I didnt see anything to reset all states in your resetLocalStorage(). I assume you will keep track of all the 'local storage keys' and define reset-functions for each. This example modifies your hook to return a third function to reset the state so another reset-function doesn't have top be defined.
https://codesandbox.io/s/smoosh-sound-0yxvl?file=/src/App.js
I made few changes in your code to achieve the use case you were trying to achieve. (Let me know my implementation is suffices your use case)
The resetLocalStorage is not starting with use That's why you were getting the previous error.
After renaming that function to useResetLocalStorage, still it will not work since -
you were calling the custom hook onClick of button. This breaks one react hook rule which is react hooks must not be called conditionally https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 2 years ago.
I was facing almost the same problem as in this question
The code is a bit too much so I have made a stripped down version of the problem. (please forgive me if I made a mistake in doing so)
Basically, I have a main component and a sub component
main component
import React {useState} from 'react'
import SubComponent from './subcomponent';
const Main = (props) => {
const [state, setState] = useState(null);
const updateStateFunction = (data) => {
const newState = data
console.log(state)
setState(newState)
console.log(state)
}
return (
<div>
<SubComponent
updateStateFunction = {updateStateFunction}
/>
</div>
)
}
export default Main;
sub component
import React {useState} from 'react'
const SubComponent = ({updateStateFunction}) => {
return (
<div>
<button
onClick={() => updateStateFunction("Something new")}
>
</button>
</div>
)
}
export default SubComponent;
both the console logs give null.
My attempts at a solution:
Since most stack overflow answers suggested that stateupdates using hooks is asynchronous I tried using setTimeout
I thought we could then use async-await but that was a wrong approach
I tried updating the state inside useEffect but the point is that nothing is being re redered. This is because the variable that is being updated is never a part of an output but rather sort a helper varibale for further operations.
The way I did this was using the solution in the above refereced question:
const Main = (props) => {
/*Debugging*/
let newState = null
useEffect(()=>{
console.log("useEffect called")
setState(newState)
}, [newState])
/*Debugging*/
const [state, setState] = useState(null);
const updateStateFunction = (data) => {
newState = data
console.log(state)
setState(newState)
console.log(state)
}
return (
<div>
<SubComponent
updateStateFunction = {updateStateFunction}
/>
</div>
)
}
I though since the useEffect hook is not even being executed hence I did not try the other two methods in the solution
Am I referencing the wrong type of problem or is this a common behaviour in react?
Happy to provide any more information if needed
Edit:
I have added console.log() because I have operations followed by the state change that uses the value of the state variable.
Using React dev tools I see that the state is updating and that too almost instantly. The problem is that the button press leads to a dialogue pop-up in the real code whose component uses the state for other logic, hence I get an error that that state is still null
I am not sure how let newState = null has anything to do with any of the answers in the quoted question, so to be clear, this is how one would directly apply the accepted answer:
const Main = (props) => {
const [state, setState] = useState(null);
const updateStateFunction = (data) => { setState(data) }
useEffect(() => { console.log(state) }, [state])
return <SubComponent updateStateFunction = {updateStateFunction} />
}
However, there is no point of changing a state if it's not used for anything rendered on the screen - if Reacts does not detect any change in the return value, it might not commit any change to the DOM as a part of the optimizations, so it would probably not run any useEffects in that case.
I would recommend using React Dev Tools for checking the current state of a Component.
Also, console.log is basically the only side effect which does not need to be inside useEffect. If directly in the render function, it would be executed whenever the render function is called and would not depend on React committing changes to DOM...
Note that the first advice in my answer to the quoted question does not wrap console.log into useEffect - and to be clear again, this is how one would directly apply that advice:
const Main = (props) => {
const [state, setState] = useState(null);
const updateStateFunction = (data) => { setState(data) }
console.log(state)
return <SubComponent updateStateFunction = {updateStateFunction} />
}
The setting of the state is asynchronous in react.
An asynchronous function will be executed in parallel (well, kind of) as other instructions. Rather than console.logging after setting state, you could console.log state before the return function to know the new output.
There is nothing wrong with your first implementation. No need to use useEffect here
import React {useState} from 'react'
import SubComponent from './subcomponent';
const Main = (props) => {
const [state, setState] = useState(null);
const updateStateFunction = (data) => {
setState(data)
}
// try console logging here
console.log(state)
return (
<div>
<SubComponent
updateStateFunction = {updateStateFunction}
/>
</div>
)
}
export default Main;
Think of it like this, whenever the state is set, your function which contains the state gets refreshed and re-run. You could console.log anywhere in the function to get the new state.
Use the react devtools to debug, even if the console.log() display null you see the state change in the devtools. The reason is that the state update is asynchronous.
If you still want to debug your react app using console.log() then call it just before the return statement or even in the jsx directly using curly braces.
I know this is horrible convention, but I'm trying to quickly conditionally render screens in my React Native app with global variables (so no redux):
App.js:
if (global.clickStatus !== 'clicked') {
return <Screen1 />;
}
return <Screen2 />;
The app begins on Screen1, where there is a button that makes global.clickStatus = 'clicked'. When this is clicked, I want Screen2 to render. The problem is, the global.clickStatus doesn't seem to update on my App.js (even though global.clickStatus is changed, it still renders Screen1.
How can I get it to update?
I believe in <App /> component because it is a function component you can introduce a state if your button is clicked. Then with clicked state you can manipulate which component to show.
Similarly like the following - obviously this is a simplified example:
const App = () => {
const [clicked, setClicked] = useState(false);
return <>
<div onClick={() => setClicked(true)}>Click me</div>
{ clicked ? <Screen2 /> : <Screen1 /> }
</>
}
Suggested read is Using the State Hook.
The app begins on Screen1, where there is a button that makes global.clickStatus = 'clicked'
When you click the button, you did not set any state for App.js component => no re-render action is made.
I just assume the button is in Screen 1. Try code below:
import React from "react";
import "./styles.css";
export default function App() {
// Create a state
const [renderIndex, setRenderIndex] = useState(new Date().getTime())
if (global.clickStatus !== 'clicked') {
// Assume you have a button in Screen1
// Pass a callback function from this component to Screen1
// When button in Screen1 is clicked, call this callback function to update renderIndex => App component will re-render
return <Screen1 callBack={() => setRenderIndex(new Date().getTime())}/>;
}
return <Screen2 />;
}