I am learning react-hooks, I created a series of state variables using useState, when trying to debug and see its value I find that React Developer Tool does not show the name assigned to the state variable but the text State, this is inconvenient as it is not possible to identify from the beginning which variable is the one that is tried to debug
Update 1
This is the current source code
import React, { useState, useEffect, Fragment } from "react";
function Main() {
const [data, setData] = useState({ hits: [] });
const [query, setQuery] = useState("redux");
const [url, setUrl] = useState(
"https://hn.algolia.com/api/v1/search?query=redux"
);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsError(false);
setIsLoading(true);
try {
const response = await fetch(url);
const json = await response.json();
setData(json);
} catch (e) {
setIsError(true);
}
setIsLoading(false);
};
fetchData();
}, [url]);
return (
<Fragment>
<form
onSubmit={event => {
setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`);
event.preventDefault();
}}
>
<input value={query} onChange={event => setQuery(event.target.value)} />
<button type="submit">Search</button>
</form>
{isError && <div>Something went wrong ...</div>}
{isLoading ? (
<div>Loading...</div>
) : (
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
)}
</Fragment>
);
}
export default Main;
I am getting this in React Developer tool
Updated 2
I am using Firefox 68
Is it possible that React Developer Tool shows the name of state variables created using useState?
See this issue:
https://github.com/facebook/react-devtools/issues/1215#issuecomment-479937560
That's the normal behavior for the dev tool when using hooks.
I asked the library author about it, cause I also would like it to show my state variable names. And that's what he said:
#cbdeveloper I haven't thought of a good way for DevTools to be able to display the variable name like you're asking. DevTools doesn't have a way to read your function's private variables, and changing the API to support passing in a display name would increase the size of component code. It also just doesn't seem necessary to me, more like a nice to have.
Anyway, this umbrella issue is not the best place to have discussions like this. If you feel strongly about it, I suggest opening a new issue.
From "normal" useState hook implementation:
const [users, setUser] = useState([]);
const [profile, setProfile] = useState([]);
const [repo, setRepo] = useState([]);
const [loading, setLoading] = useState(false);
const [alert, setAlert] = useState(false);
You can "convert" it in:
const [state, setState] = useState({ users: [], profile: [], repo: [], loading: false, alert: false });
And you will get the following result:
And to set the state you can use the rest operator(source 1 / source 2) and the state you want to set:
// ...state => unchanged states
// alert => state we want to change
setState(state => ({ ...state, alert: true }));
to use it as a prop:
const {
users,
profile,
repo,
loading,
alert
} = state;
<SomeComponent loading={loading} alert={alert} />
You see the React docs here and search for: Should I use one or many state variables?
You can use useDebugValue to display a custom label in your own hook:
const format = ({ label, value }) =>
label + ': ' + (typeof value === 'object' ? JSON.stringify(value) : value);
const useNamedState = (label, initialState) => {
const states = useState(initialState);
useDebugValue({ label, value: states[0] }, format);
return states;
};
Usage
Before
const [name, setName] = useState('bob');
const [age, setAge] = useState(11);
const [address, setAddress] = useState('London, United Kingdom');
const [isVirgin, setIsVirgin] = useState(false);
const [isMale, setIsMale] = useState(true);
const [hobbies, setHobbies] = useState(['gaming', 'hiking', 'cooking']);
const [friendList, setFriendList] = useState(['bob']);
After
const [name, setName] = useNamedState('name', 'bob');
const [age, setAge] = useNamedState('age', 11);
const [address, setAddress] = useNamedState('address', 'London, United Kingdom');
const [isVirgin, setIsVirgin] = useNamedState('isVirgin', false);
const [isMale, setIsMale] = useNamedState('isMale', true);
const [hobbies, setHobbies] = useNamedState('hobbies', ['gaming', 'hiking', 'cooking']);
const [friendList, setFriendList] = useNamedState('friendList', ['bob']);
Hmm, I'm not sure why it doesn't show the names, but if I'm trying to create a series of state variables, I sometimes just initialize a state and setState variable as an empty object. I'm not sure if this is the best way, but maybe in your dev tools, it'll show the state attribute name when you expand it.
import React from "react";
export default function App(props) {
// Initializing the state
const [state, setState] = React.useState({});
// Changing the state
setState({ ...state, text1: "hello world", text2: "foobar" });
return (
<div>
{state.text1}
<br />
{state.text2}
</div>
);
}
Related
I have a clearState function which sets some useState hooks back to their initial state when the restart button is clicked. However, they say that my setState is not a function. Please check code below:
App.js
...
const [question, setQuestion] = useState(0);
const [response, setResponse] = useState({});
const [answer, setAnswer] = useState({});
const [answerId, setAnswerId] = useState({});
...
Modal.js
const Modal = ({
setResponse,
setAnswer,
setAnswerId,
setQuestion,
setAnswerNameArr,
}) => {
const [open, setOpen] = useState(false);
const clearState = () => {
setOpen(false); //works
setQuestion(0); //works
setAnswer({}); //does not work
setAnswerId({});
setResponse({});
setAnswerNameArr([]);
};
...
return (
<Modal
...
>
...
<Button
onClick={()=>handleSubmit()}
>
Restart
</Button>
</Modal>
);
};
export default Modal;
The error:
Uncaught TypeError: setAnswer is not a function
Thanks in advance.
It looks like you aren't passing your state setting hooks in to your <Modal> so they're not available.
It isn't a good idea to do that anyway, tbh. If you need a child to affect the state of a parent it would be better to pass a single call-back:
const Modal = ({
onSubmitCb
}) => {
const [open, setOpen] = useState(false);
const clearState = () => {
setOpen(false); //works
setQuestion(0); //works
onSubmitCb && onSubmitCb()
};
...
return (
<Modal>
...
<Button
onClick={()=>handleSubmit()}
>
Restart
</Button>
</Modal>
);
};
and in your parent:
const App = ()=>{
const clearState = () => {
setAnswer({});
setAnswerId({});
setResponse({});
setAnswerNameArr([]);
};
....
return {
<Modal ... onSubmitCb={clearState} />
}
}
How can I safely use an object in a component if the state is not set yet?
I initialize it as null (I could use {} but that causes other effects). I get:
Cannot read property 'name' of undefined
const [user, setUser] = useState(null);
someService(user => setUser(user))
const someRef = useRef(null);
if (!user) {
// to avoid the undefined user.name I tried this but it causes an issue with useRef (because the actual someRef is not rendered when !user
return <p>loading user</p>
}
return (
<div>
<p>{user.name}</p>
<div ref={someRef}>hello</div>
</div>
)
working example: https://codesandbox.io/s/young-dust-y2z5k?fontsize=14&hidenavigation=1&theme=dark
at this point the user is not created and we should render the "loading user" this is fine but someRef is causing issues because there are messages but the component is not rendered. uncomment line 5 and comment line 6 to see it working
try:
const [user, setUser] = useState(null);
someService(user => setUser(user))
render(){
return({user? <p>{user.name}</p> :<p>loading user</p>});
}
You can use useEffect on ref and user change to set another variable so:
import React, { useState, useEffect } from 'react'
export default function Test() {
const [user, setUser] = useState(null);
const someRef = useRef(null);
const [loaded, setLoaded] = useState(false);
useEffect(() => {
if (user && someRef) {
setLoaded(true)
}
}, [user, someRef])
return (
loaded ?
<div>
<p>{user.name}</p>
<div ref={someRef}>hello</div>
</div> : <div>not loaded yet</div>
)
}
I'm having an issue when repopulating form for edit using react hooks.
Parent Component : Edit.js
const EditData = (props) => {
const { Id } = props.match.params;
const dispatch = useDispatch();
// calling redux action to get the data
useEffect(() => {
dispatch(getDataById(Id));
}, [Id]);
const data = useSelector((state) => state.data);
const initialState = {
Id: data.cardId || '',
Number: data.Number || '',
Date: data.Date,
};
//calling custom hook
const { handleChange, handleSubmit, values,errors } = useForm(
initialState,// passing initial state to custom hook
validateOnSubmit,
submit
);
// used to submit the data
function submit() {
dispatch(updateCard(values));
}
return (<DateForm
handleSubmit={handleSubmit}
handleChange={handleChange}
values={values}
/>);
};
Custom hook: useform.js
const useForm = (initialState, validateOnSubmit, callback) => {
const [values, setValues] = useState(initialState);
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const handleChange = (event) => {
const { name, value } = event.target;
setValues({
...values,
[name]: value
});
};
const handleSubmit = (event) => {
event.preventDefault();
setErrors(validateOnSubmit(values));
setIsSubmitting(true);
};
useEffect(() => {
if (Object.keys(errors).length === 0 && isSubmitting) {
callback();
}
}, [errors]);
return {
handleChange,
handleSubmit,
values,
errors
};
};
when the API call get finished the react re-render the custom but the local state of the hook is not updating.
const useForm = (initialState, validateOnSubmit, callback) => {
console.log(initialState);
on second render, here i can receive data from the API
const [values, setValues] = useState(initialState);
but values is not getting updated, values is still holding the state from the initial render
I cannot figure out why this is. I'm just started to use react hooks, please help me.
As OP stated in a comment:
the initialState variable is updated when the API call get completed,I'm passing that initialState variable to const [values, setValues] = useState(initialState); , so it should update the values variable right?. but it's not!
It is should update the state, the initial state is assigned once until the component unmounts.
See useState API, it stated in lazy initialization:
The initialState argument is the state used during the initial render. In subsequent renders, it is disregarded.
I'm using React right now and I'm trying to get my localstorage to update a state once the event handles a return on search and then hold that state until the next search is completed. Right now I can't figure out where to put an event handler that triggers the correct state and holds the correct value.
const useStateWithLocalStorage = localStorageKey => {
const [value, setValue] = React.useState(
localStorage.getItem(localStorageKey) || ''
);
React.useEffect(() => {
localStorage.setItem(localStorageKey, value);
}, [value]);
return [value, setValue];
};
export default function App() {
const [value, setValue] = useStateWithLocalStorage(
'myValueInLocalStorage'
);
const onChange = event => setValue(event.target.value);
const [state, setState] = useState({
message: 'test deploy',
results: [],
value: '',
});
...
and where I'm trying to implement the event handler
export default function SearchAppBar(props) {
const classes = useStyles();
const [searchTerm, setSearchTerm] = useState('');
const { onClick } = props;
...
<InputBase
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search…"
classes={{
root: classes.inputRoot,
input: classes.inputInput,
}}
inputProps={{ 'aria-label': 'search' }}
/>
<Button onClick={() => onClick(searchTerm)}> Search </Button>```
Hereby my solution. I've created an useLocalStorage function that stores and gets or sets items in the local storage and holds them in its own state:
import React from "react";
export const useLocalStorage = (key, initialValue) => {
const [storedValue, setStoredValue] = React.useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.log(error);
return initialValue;
}
});
const setValue = value => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.log(error);
}
};
return [storedValue, setValue];
};
export default useLocalStorage;
For the searchBar component I've used a forwardRef to access the value of the input inside our higher component App. The newSearch function and searchTerm variable are destructured off the props. The placeholder holds the stored value in localStorage, which is searchTerm:
export const SearchAppBar = React.forwardRef(
({ newSearch, searchTerm }, ref) => {
return (
<>
<input ref={ref} type="text" placeholder={searchTerm} />
<button onClick={newSearch}> Search </button>
</>
);
}
);
Inside the main App component I'm using our useLocalStorage function hook to get and set the search. Inside newSearch I'm updating the search term by calling our hook with the value of the forwarded input ref.
export default function App() {
const ref = React.createRef();
const [searchTerm, setSearchTerm] = useLocalStorage(
"search",
"Not searched yet"
);
const newSearch = () => {
setSearchTerm(ref.current.value);
};
return (
<>
<SearchAppBar ref={ref} newSearch={newSearch} searchTerm={searchTerm} />
<p>Last search: {searchTerm}</p>
</>
);
}
Hope this is a workable solution for you.
Please find a code snippet here:
https://codesandbox.io/s/cranky-sunset-8fqtm?file=/src/index.js:387-773
I like the approach used by redux to handling the states on react. I use redux with redux-persist library to save the state instead of localStorage. If your project grows and you need to work with more complex states, it could help you.
I'm trying to upload a picture using react hooks
const [picture, setPicture] = useState();
const onChangePicture = e => {
console.log('picture: ', picture);
setPicture(...picture, e.target.files[0]);
};
<input
type="file"
//style={{ display: 'none' }}
onChange={e => onChangePicture(e)}
/>
however I'm getting the following error:
Uncaught TypeError: picture is not iterable
when I change the onChangePicture to
setPicture(picture, e.target.files[0])
the picture variable is undefined,
any help would be appreciated.
I think you meant to do:
setPicture([...picture, e.target.files[0]]);
This will concatenate the first file to all current files.
Remember to use const [picture, setPicture] = useState([]); as to make sure it doesn't break the first time around
For anybody arriving here looking for how to do it with TypeScript:
const [file, setFile] = useState<File>();
const onChange = (event: React.FormEvent) => {
const files = (event.target as HTMLInputElement).files
if (files && files.length > 0) {
setFile(files[0])
}
}
You can pass the value directly into setPicture function to set the state variable picture.
Try:
const [picture, setPicture] = useState(null);
const onChangePicture = e => {
console.log('picture: ', picture);
setPicture(e.target.files[0]);
};
<input
type="file"
//style={{ display: 'none' }}
onChange={onChangePicture}
/>
onChange = {(e) => this.onChangePicture(e)} can only be written when you made the states as
states = {
image,
name
}
but when using useState()
you need to use
const [image, setImage] = useState("");
onChange = {(e) => setImage(e.target.files[0])}
I hope this solves the error.
I finally fix this issue:
Problem is here
const [picture, setPicture] = useState(null); //Incorrect
You can use this
const [picture, setPicture] = React.useState(""); //Correct
This can fix this issue