I have a little question about re-render with useState.
For example, I have five useState methods:
setFirstState(someValue);
setSecondState(someValueSecond);
setThirdState(someValueThird);
...
And I want for this all just one render with react hooks. Can you explane me or maybe some example?
You can combine it with useEffect with empty deps.
import { useState, useEffect } from 'react';
const MyComp = () => {
const [val, setVal] = useState('initialVal');
useEffect(() => {
setVal('newVal');
}, []);
// --^ - this is an empty dependency list
return <div>Hello</div>;
};
You can combine all those into one state.
setCombinedState({
first: someValue,
second: someValueSecond,
third: someValueThird,
});
:) Hope that will solve your problem.
You would like to use useReducer instead, and initiate it with an object with your (first, second, third...) properties, and then modify the one you want each time.
Declare your initial values
const initialState = {
someValue: 'someValue',
someValueSecond: 'someValueSecond',
someValueThird: 'someValueThird',
}
set it
const [val, setVal] = React.useReducer(initalState);
And modify then whenever you need it.
setVal({someValue: 'anotherValue'});
setVal({someValueSecond: 'anotherValue'});
setVal({someValueThird: 'anotherValue'});
Usually, when you want to change multiple states at the same time, it usually means that these states are couples in some manner.
Example
Take for instance this case: You want to fetch some data from an API, so you would have the following states:
const [loading, setLoading] = useState(false)
const [error, setError] = useState(false)
const [data, setData] = useState(undefined)
Usually when one of these states changes, the others do as well.
This is a clear case where you should use useReducer.
For instance, like this:
const reducer = (state, action) => {
switch (action.type) {
case 'error':
return {error: action.payload.error, loading: false };
case 'fetched':
return {data: action.payload.data, loading: false };
case 'loading':
return {loading: true};
default:
throw new Error();
}
}
And after this, you could make a custom hook to manage these states:
const {loading, error, data, dispatch} = useFetchingData()
To sum up
This is just an example, but I would recommend you to take a look at your case, and see if these states you are talking about are somehow connected, and could be better arranged in this manner.
Reducers need more code that the one I posted here. I just wrote the reducer function. If you want to know more about how they work, I recommend you to read the docs.
If you want to update your question with the actual case you are facing right now, I could help you further.
Related
I decided to add Redux to my pet project (surprise, todolist).
Here's add entry function:
const [todoEntry, setTodoEntry] = useState('');
const addNewEntry = (e) => {
e.preventDefault();
// console.log(todoEntry);
dispatch({
type: ADD_TODO,
payload: {
prodName: todoEntry,
done: false,
favorite: false,
edit: false,
id: uuid()
}
})
setTodoEntry('');
todoEntry comes from another component like that:
<input
id='standartInput'
style={{minWidth: '250px'}}
value={todoEntry}
onChange={e => setTodoEntry(e.target.value)}
type='text'
placeholder='Add new entry (max 55 symbols)' />
Also, I use some hooks to manage my state:
const myTodoItems = useSelector((state) => state.todos[0])
const dispatch = useDispatch()
const [data, setData] = useState(myTodoItems);
And, finally, the reducer:
import { todolist } from "./todolist"
import { ADD_TODO } from '../Store/todoactions'
export const todoReducer = (state = [todolist], action) => {
switch (action.type) {
case ADD_TODO: {
const newItem = action.payload
console.log(newItem)
console.log(todolist)
return ([...todolist, newItem])
}
default:
{ return state }
}
}
The issue is:
todolist exists, I can see at browser console
newItem exists too, I also can see at browser console
BUT! When clicking on 'Add' button, state is not updated.
What I'm doing wrong?
Thanks.
const myTodoItems = useSelector((state) => state.todos[0])
You seem to be selecting only the first item, so it's not surprising that you don't see the rest.
const [data, setData] = useState(myTodoItems);
This looks like an antipattern, why do you need a state variable for something that is already tracked by Redux?
You should also use Redux Toolkit, it is not recommended to use Redux directly.
Edit:
Thank you for the codesandbox, now the problem is clear.
You are using both Redux and React state to deal with the same data, for instance you add todos through Redux but complete them with React state.
A very important principle in React/Redux is to have a single source of truth, but in your case you have two sources of truth for the todos: the Redux store and the useState hook. You use the React state for rendering and initialize it with the Redux state, but then you don't update it when a todo is added, so the UI shows outdated information.
It's fine to use sometimes Redux, sometimes useState as long as it is for independent pieces of data, but for the same data, you need to choose.
Remember that everytime you use useState(initialState) you create a new state variable/source of truth, which will become different from the initial state. Sometimes this is exactly what you want, but not here.
So I would suggest to remove the useState and go through Redux for everything you want to change about the todos (edit them, complete them, and so on).
There are some things you can have as React state (for instance the boolean "are we currently editing this specific todo item"), but then it would be much easier to have a simple useState(false) directly in the TodoItem component.
Is it a problem/bad habit to add codes that does conditional rendering based on states or values that update constantly outside the useEffect hook.
Like this
function Home() {
const { state } = useLocation();
const [validation, setValidation] = useState();
const [MainData, setMainData] = useState();
if(true)
do somthing....
else
do somthing....
React.useEffect(() => {
something else.....
})
return (
);
}
Or should i do it this way??
function Home() {
const { state } = useLocation();
const [validation, setValidation] = useState();
const [MainData, setMainData] = useState();
React.useEffect(() => {
if (true)
do somthing....
else
do somthing....
something else....
})
return (
);
}
But if it do it the second way sometimes i get warnings about dependencies. Is that like a major problem should i just ignore it??
Yes, both examples definitely are bad habits.
The statements in both will be run on every re-render since you didn't add a dependency array in your second example. You should use the useEffect hook with a dependency array and add the dependencies (or leave an empty array if you only want to run the code on the first mount of the component). In my experience there barely is any case where re-calculations on every re-render are necessary and I had to find out the hard way that such calculations can really hurt the performance of your app, especially when using concurrent react.
If you want to declare an arrow function in your component or calculate a value that you want to render I advice you to use the useCallback or useMemo hook.
I am trying to build my very own first project that is relatively large, at least for my level of experience anyway.
I am heavily relying on useContext in combination with useStates hooks to handle the logic between my different components, as time goes, it's really starting to get difficult to keep track of all these different states changes and simple onClick events I have to change the logic of a large number of states.
Hoping for a little bit of personal advice that could steer me in the right direction.
as somehow what I do, doesn't feel normal, or this is the reality of React?
Surely there are more clever ways to reduce the amount of state logic management?
Here are a few snippets of code I am using
const onClick = (note: INote) => {
SetAddNote(false);
SetNote(note);
onSelected(note)
SetReadOnly(true);
SetEditor(note.data.value);
SetInputValue(note.data.name);
SetCategory(note.data.category);
};
const { note, noteDispatch, SetNoteDispatch } = useContext(NoteContext);
const { categories } = useContext(CategoriesContext);
const [ editMode, setEditMode ] = useState(false);
const [ module, setModule ] = useState<{}>(modulesReadOnly)
const [inputValue, setInputValue] = useState<string>('');
const [category, setCategory] = useState('');
const [color, setColor] = useState('');
import React, { createContext, useState } from 'react';
type EditorContextType = {
editor: string;
SetEditor: React.Dispatch<React.SetStateAction<string>>;
readOnly: boolean;
SetReadOnly: React.Dispatch<React.SetStateAction<boolean>>;
inputValue: string;
SetInputValue: React.Dispatch<React.SetStateAction<string>>;
category: string;
SetCategory: React.Dispatch<React.SetStateAction<string>>;
};
type EditorContextProviderProps = {
children: React.ReactNode;
};
export const EditorContext = createContext({} as EditorContextType);
export const EditorContextProvider = ({
children,
}: EditorContextProviderProps) => {
const [editor, SetEditor] = useState('');
const [readOnly, SetReadOnly] = useState(false)
const [inputValue, SetInputValue] = useState('');
const [category, SetCategory] = useState('');
return (
<EditorContext.Provider value={{ editor, SetEditor, readOnly, SetReadOnly, inputValue, SetInputValue, category, SetCategory }}>
{children}
</EditorContext.Provider>
);
};
Sure I could shave a few states and merge them into one, but seems like that would get even more complex than it is.
I am reading about the useReducer hook however it's difficult to grasp the entire idea behind it yet and not quite sure if it's really going to help me in this case.
It feels to me I am setting myself for a failure given I continue working in this fashion, but I don't see any better options to implement
I have working on big project too, and as you say in your question, Reducer will help you to fix your issue, but surly you need to be careful how you will build and manage your state, so the idea how you manage a state, so before I put my answer, I will write some important note:
Make sure to reduce nested context as you can, only build context and use context when theres a needed for that, this will optomize your work
For handling or merge state, you can use object, arrays and normal variable, but keep in your mind, try to prevent nested level of objects, to keep state update on state change.
Use reducer to handling update on state will give you a nice ability
We can do some tricks to improve performance like set condition in reducer which its check old state and new state.
Keep in your mind, really its easy to use it, but the first time its hard to learn...
Now lets start from a real project example:
// create VirtualClass context
export const JitsiContext = React.createContext();
// General Action
const SET_IS_SHARED_SCREEN = 'SET_IS_SHARED_SCREEN';
const SET_ERROR_TRACK_FOR_DEVICE = 'SET_ERROR_TRACK_FOR_DEVICE';
const UPDATE_PARTICIPANTS_INFO = 'UPDATE_PARTICIPANTS_INFO';
const UPDATE_LOCAL_PARTICIPANTS_INFO = 'UPDATE_LOCAL_PARTICIPANTS_INFO';
// Initial VirtualClass Data
const initialState = {
errorTrackForDevice: 0,
participantsInfo: [],
remoteParticipantsInfo: [],
localParticipantInfo: {}
};
// Global Reducer for handling state
const Reducer = (jitsiState = initialState, action) => {
switch (action.type) {
case UPDATE_PARTICIPANTS_INFO:// Update particpants info and remote list
if (arraysAreEqual(action.payload, jitsiState.remoteParticipantsInfo)) {
return jitsiState;
}
return {
...jitsiState,
participantsInfo: [jitsiState.localParticipantInfo, ...action.payload],
remoteParticipantsInfo: action.payload,
};
case UPDATE_LOCAL_PARTICIPANTS_INFO:// Update particpants info and local one
if (JSON.stringify(action.payload) === JSON.stringify(jitsiState.localParticipantInfo)) {
return jitsiState;
}
return {
...jitsiState,
localParticipantInfo: action.payload,
participantsInfo: [action.payload, ...jitsiState.remoteParticipantsInfo],
};
case SET_IS_SHARED_SCREEN:
if (action.payload === jitsiState.isSharedScreen) {
return jitsiState;
}
return {
...jitsiState,
isSharedScreen: action.payload,
};
default:
throw new Error(`action: ${action.type} not supported in VirtualClass Context`);
}
};
const JitsiProvider = ({children}) => {
const [jitsiState, dispatch] = useReducer(Reducer, initialState);
// Update shared screen flag
const setIsSharedScreen = useCallback((flag) => {
dispatch({type: SET_IS_SHARED_SCREEN, payload: flag})
}, []);
// Update list of erros
const setErrorTrackForDevice = useCallback((value) => {
dispatch({type: SET_ERROR_TRACK_FOR_DEVICE, payload: value})
}, []);
// Local Participant info
const updateLocalParticipantsInfo = useCallback((value) => {
dispatch({type: UPDATE_LOCAL_PARTICIPANTS_INFO, payload: value})
}, []);
const updateParticipantsInfo = useCallback(async (room, currentUserId = null) => {
if (!room.current) {
return;
}
// get current paricipants in room
let payloads = await room.current.getParticipants();
// ... some logic
let finalResult = payloads.filter(n => n).sort((a, b) => (b.startedAt - a.startedAt));
dispatch({type: UPDATE_PARTICIPANTS_INFO, payload: finalResult})
}, []);
const contextValue = useMemo(() => {
return {
jitsiState,
setIsSharedScreen,
setErrorTrackForDevice,
updateParticipantsInfo,
updateLocalParticipantsInfo,
};
}, [jitsiState]);
return (
<JitsiContext.Provider
value={contextValue}
>
{children}
</JitsiContext.Provider>
);
};
export default JitsiProvider;
This example allow you to update state and you have more than one case, all state value share by jitsiState, so you can get any data you want, and about function, you can use dispatch direct! but in our experience we build a callback method and send it via provider too, this give us upillty to control code and logic in one place, and make process very easy, so when click in every place just we call needed method...
You will see also conditions and useMemo...these to prevent render un-needed trigger, like change the key in memory not the real value and so on...
Finally after we use it, we control now all the state between all component too easy, and we didn't have nested context except the wrapper context.
Note: surly you can skip or change this code base on your logic or needed concepts.
Note 2: this code is catted and do some changes to make it easy to read or understand...
Note 3: You can ignore all functions pass in provider and use dispatch direct, but in my project I send a function like this example.
I'm using react hooks in React Native.
My problem is that the function of useState which to initialize state makes re-render.
So if I set state like below
const [A, setA] = useState(false);
const [B, setB] = useState(false);
const [C, setA] = useState(false);
// ...
const testFunc = () => {
setA(true);
setB(true);
setC(true);
}
EDITED
I think examples were wrong.
Here's another example.
const useFetch(coords) {
const [example, setExample] = useState([])
const [checker, setChecker] = useState(false);
const fetchData = () => {
axios.fetch(`url+${coords.latitue}+${coords.longitude}`).then(){
setExample(res.data());
setChecker(true);
}
}
useEffect(() => {
fetchData();
}, [coords])
return example;
}
const useLocation = () => {
...
return coords;
}
const App = () => {
const coords = useLocation();
const example = useFetch(coords); // example is undefined.
const [data, setData] = useState(example); // data is undefined.
}
It causes many re-render as many as I use the set function.
Is this natural thing?
If I don't want to make this re-render, can't use the set function multiple times?
You can not do it in straightforward way. I will suggest you the two solutions for it.
Solution 1: Combine states in one object.
const [value, setValue] = useState({A: false, B: false, C: false});
// ...
const testFunc = () => {
setValue({A: true, B: true, C: true});
}
Solution 2: Another solution is useReducer.
const [state, setState] = useReducer(
(state, newState) => ({...state, ...newState}),
{A: false, B: false, C: false}
);
// ...
const testFunc = () => {
setState({A: true, B: true, C: true});
}
Here I have implemented your another example: https://stackblitz.com/edit/react-usestate-wcjshg
Hope this will help for you!
React does not batch state updates if they are triggered outside React-based event. That means, if you want your state updates to be batched you need to wrap it on an event handle such as onClick.
If your local component state is non-trival and/or using an event handler is not an option, I'd recommend you to use useReducer as you can batch your state updates within that.
This appears to be normal React behavior. It works the exact same way if you were to call setState() in a class component multiple times.
React currently will batch state updates if they're triggered from within a React-based event, like a button click or input change. It will not batch updates if they're triggered outside of a React event handler, like a setTimeout().
I think there's plans long-term to always batch events, but not sure on the details
Sources:
https://github.com/facebook/react/issues/14259#issuecomment-439632622
https://github.com/facebook/react/issues/14259#issuecomment-468937068
As stated in the other answers, React does not batch state updates if they are triggered outside React-based events (in then for example), one of the solutions is to merge your state in one object and call setState one time. But if you like to keep your state separated, the solution is to use ReactDOM.unstable_batchedUpdates like this :
const fetchData = () => {
axios.fetch(`url+${coords.latitue}+${coords.longitude}`).then(() => {
ReactDOM.unstable_batchedUpdates(() => {
setExample(res.data());
setChecker(true);
});
});
}
Recommended by Dan Abramov here
I've recently started to try to learn React hooks, but for the life of me I can't figure out some things, like multiple state management, I've found a few example but nothing seems to be in a specific pattern. I'm not even sure how you're supposed to define your states if there's more than 2 and you want to change them individually, should it be like this:
const [slides, setSlides] = useState([])
const [currentSlide, setCurrentSlide] = useState(0)
const [tagName, setTagName] = useState([])
Or like this:
const [states, setStates] = useState({
slides: [],
currentSlide: 0,
tagName: []
})
And if both or the second one is viable (honestly I would prefer the second one since its less repetitive in calling useState and closer to the standard state meta) how could one go about changing states in such a example? I couldn't really find anything on this, so what I tried was this:
useEffect(() => {
Object.entries(Slides).forEach(slide => {
setStates(prevState => ({ slides: [...prevState.slides, slide[1]] }))
})
}, [])
And I messed around with it a bunch trying to get some decent results but I couldn't get it to work properly.
Any ideas of what I'm doing wrong? And on which one of these to methods of best practice?
Thanks!
In terms of updating state, first template is much easy and simple, because each stateUpdate function will be responsible for updating single value, that can be obj/array/int/bool/string. Another thing is, to access each value of the state (it will be an object), you need to write states.slides, states.tagName etc.
With first case, state update will be simply:
// only value in case of int/string/bool
updateState({ [key]: value }) or updateState(value);
But in second case, state will be an object with multiple keys, so to update any single value you need to copy the object, then pass the updated key-value. Like this:
updateState({
...obj,
[key]: newValue
})
To update the slides array:
updateState(prevState => ({
...prevState,
slides: newArray
}))
Handling complex state update:
Use useReducer to handle the complex state update, write a separate reducer function with action types and for each action type so the calculation and return the new state.
For Example:
const initialState = { slides: [], tagName: [], currentSlide: 0 };
function reducer(state, action) {
switch (action.type) {
case 'SLIDES':
return { ... };
case 'TAGNAME':
return { ... };
case 'CURRENT_SLIDE':
return { ... }
default:
throw new Error();
}
}
function Counter({initialState}) {
const [state, dispatch] = useReducer(reducer, initialState);
....
}
It is true that the useState hook can become quite verbose when dealing with complex state object.
In this case, you can also consider using the useReducer hook.
I would recommend the reading of this article about useState vs useReducer.