How to make State update when only a string array is update - reactjs

Guys i have this example code bellow:
const [data, setData] = useState([{question: '', option: ['']}]);
Then data and setData will pass to my component, like:
<Question setData={setData} data={data}/>
My code inside Question component is:
const handleAddOption = (questionIndex: number) => {
let newArray = data;
newArray.map((item: any, i: number) => {
if (i === questionIndex) {
newArray[questionIndex].options.push('');
}
});
setData(newArray);
};
The problem is, if i add a new entire Object it will "refresh" my page and show, but, when i add like the last lines of code, only a new string inside the array, it will not "re-render".
Anyone knows how to solve this?

In react first rule of thumb is don't mutate state directly. It works for class-based components and setState, it works for redux's reducers, it works for hook-based useState too.
You need to
setData((data) => data.map((item, index) => {
if (index !== questionIndex) return item;
return {
...item,
options: [
...item.options,
''
]
};
}));
There are several items to highlight here:
as well as for class-based component's setState there is possible to provide callback into updater function. I better skip describing it in details here. May suggest to take a look into SO question and ReactJS docs. Yes, it's not about hooks but it uses the same logic.
We need to merge our old data with changes to keep properties we don't want to change still present. That's all this spread operator is so hardly used. Take a look into article on handling arrays in state for React
Last but not least, we have check to directly return item if it's not we want to update without any change. This makes us sure no related children components will re-render with actually exact the same data. You may find additional information by searching for query "referential equality"(here is one of article you may find).
PS it may look much easier to force update instead of rewriting code completely. But mutating state directly typically later ends up with different hard-to-reproduce bugs.
Also you may want to change components hierarchy and data structure. So no component would need to traverse whole array to update single nested property.

It seems like You have typo write newArray[questionIndex].option.push(''); instead of newArray[questionIndex].options.push('');
But if it doesn't help try forceUpdate(); more details You can find in this answer How can I force component to re-render with hooks in React? or try to use this package https://github.com/CharlesStover/use-force-update
Good Luck :)

Rough implementation
I think you can change:
const [data, setData] = useState([{question: '', option: ['']}]);
// into
const [questions, setQuestions] = useState([]);
And as you receive new question objects, you can do setQuestions([...questions, newQuestion]).
That is, assuming, you have a form that is receiving inputs for new questions. If this is the case, in your onSubmit function for your form, you can generate your new question object and then do setQuestions([...questions, newQuestion]).

Related

Alternative way to handle state in React using memo

When it comes to handling complex state in React everybody suggests to flatten the state to avoid something like this just to update a single property:
setState({ …state, user:{ …state.user, profile:{…state.user.profile, address:{…state.user.profile.address, city:’Newyork’}} }});
Which is really cubersome to work with. There is another way: use an object holding your state and return that from a memoized function. Then whenever you made a change simply force a re-render.
// note the reference cannot be changed, but values can.
const data = useMemo(() => ({
user: {
name: "",
profile: {
address: {
city: "New york"
}
}
}
}), []);
// use dummy data to trigger an update
const [toggle, setToggle] = useState(false);
function forceUpdate() {
setToggle(prev => !prev);
}
function makeChanges() {
// make any change on data without any copying.
data.user.address.city = "new city name";
// hydrate the changes to the view when you're done
forceUpdate();
}
return (
<div onClick={() => makeChanges()}>{data.user.address.city }</div>
)
Which works perfectly. Even with massive and complex data structures.
From what I can tell state is really just a memoized values which will trigger an update upon change.
So, my one question: What is the downside of using this?
The docs say useMemo is not a guarantee:
You may rely on useMemo as a performance optimization, not as a semantic guarantee. In the future, React may choose to “forget” some previously memoized values [...]
If you'd really want to do something like this and you are absolutely positively sure you're willing to do things unlike anyone else in React land, you'd use useRef for state storage that doesn't cause rerenders by itself. I'm not going to add an example of that, because I don't recommend it in the least.
You should also note that your method will not cause memoized (React.memo()) components to rerender, since they will not "see" changes to props if their identity does not change. Similarly, if another component uses one of your internally mutated objects as a dependency for e.g. an effect, those effects will not fire. Finding bugs caused by that will be spectacularly annoying.
If modifying deep object structures is otherwise cumbersome, see e.g. the Immer library, which does Proxy magic internally to let you modify deep objects without trouble – or maybe immutability-helper if you're feeling more old-school.

How correct change useState ( array, obj and other)?

I'm learning React and trying to figure out how to properly change the state in functional components. Can you please tell me if there is a difference between these two options? And if there is, which one is preferable to use in work? Thanks!
const addNewTaskInState = (subtitle: string) => {
setTodoListStateArray((todoListStateArray) => {
const newTask = addNewTask(subtitle);
return [...todoListStateArray, newTask]
})
}
const addNewTaskInState = (subtitle: string) => {
const newTask = addNewTask(subtitle);
setTodoListStateArray([...todoListStateArray, newTask])
}
The first approach name updater function you can read here Updating state based on the previous state
And your question is answer below of that sections:
Can you please tell me if there is a difference between these two options?
In most cases, there is no difference between these two approaches
which one is preferable to use in work?
If you prefer consistency over slightly more verbose syntax, it’s reasonable to always write an updater if the state you’re setting is calculated from the previous state. If it’s calculated from the previous state of some other state variable, you might want to combine them into one object and use a reducer.
I think 2nd one is more efficient you can use it. if you want to adopt another approach have a look at this code
const [state, setState] = useState([])
function SetMyState (obj) {
let auxiliaryArray = [...state]
auxiliaryArray.push(obj)
setState(auxiliaryArray)
}

Create and Read State for thousands of items using Recoil

I've just started using Recoil on a new project and I'm not sure if there is a better way to accomplish this.
My app is an interface to basically edit a JSON file containing an array of objects. It reads the file in, groups the objects based on a specific property into tabs, and then a user can navigate the tabs, see the few hundred values per tab, make changes and then save the changes.
I'm using recoil because it allows me to access the state of each input from anywhere in my app, which makes saving much easier - in theory...
In order to generate State for each object in the JSON file, I've created an component that returns null and I map over the initial array, create the component, which creates Recoil state using an AtomFamily, and then also saves the ID to another piece of Recoil state so I can keep a list of everything.
Question 1 Is these a better way to do this? The null component doesn't feel right, but storing the whole array in a single piece of state causes a re-render of everything on every keypress.
To Save the data, I have a button which calls a function. That function just needs to get the ID's, loop through them, get the state of each one, and push them into an Array. I've done this with a Selector too, but the issue is that I can't call getRecoilValue from a function because of the Rules of Hooks - but if I make the value available to the parent component, it again slows everything right down.
Question 2 I'm pretty sure I'm missing the right way to think about storing state and using hooks, but I haven't found any samples for this particular use case - needing to generate the state up front, and then accessing it all again on Save. Any guidance?
Question 1
Get accustomed to null-rendering components, you almost can't avoid them with Recoil and, more in general, this hooks-first React world 😉
About the useRecoilValue inside a function: you're right, you should leverage useRecoilCallback for that kind of task. With useRecoilCallback you have a central point where you can get and set whatever you want at once. Take a look at this working CodeSandbox where I tried to replicate (the most minimal way) your use-case. The SaveData component (a dedicated component is not necessary, you could just expose the Recoil callback without creating an ad-hoc component) is the following
const SaveData = () => {
const saveData = useRecoilCallback(({ snapshot }) => async () => {
const ids = await snapshot.getPromise(carIds);
for (const carId of ids) {
const car = await snapshot.getPromise(cars(carId));
const carIndex = db.findIndex(({ id }) => id === carId);
db[carIndex] = car;
}
console.log("Data saved, new `db` is");
console.log(JSON.stringify(db, null, 2));
});
return <button onClick={saveData}>Save data</button>;
};
as you can see:
it retrieves all the ids through const ids = await snapshot.getPromise(carIds);
it uses the ids to retrieve all the cars from the atom family const car = await snapshot.getPromise(cars(carId));
All of that in a central point, without hooks and without subscribing the component to atoms updates.
Question 2
There are a few approaches for your use case:
creating empty atoms when the app starts, updating them, and saving them in the end. It's what my CodeSandbox does
doing the same but initializing the atoms through RecoilRoot' initialState prop
being updated by Recoil about every atom change. This is possible with useRecoilTransactionObserver but please, note that it's currently marked as unstable. A new way to do the same will be available soon (I guess) but at the moment it's the only solution
The latter is the "smarter" approach but it really depends on your use case, it's up to you to think if you really want to update the JSON at every atom' update 😉
I hope it helps, let me know if I missed something 😊

Whether it is good to do lot of functionality in the setState Callback?

I am trying to check and uncheck a checkbox based on other conditions in screen..
I am printing an element using document.getElementById('foo') this is returning null even my element is present in screen and in DOM.
Please help me to solve problem.
I am developing code in which after API is being fetched state variable need to be set and do other functionality based on the respective state variable.
Is it feasible to do most of the logic inside the call back of the setState to promote synchronus way of coding or any other concepts are present to do the same?
this.setState({
filter:filterValue
},function(){
// Most of the coding logic goes here
})
Please suggest a prominent way if it is wrong
Well you definitely shouldn't use document.getElementById since it's against reactive programming logic but it's hard to say where is a problem if you don't provide example code.
Try to implement checkbox in React way:
const CheckBoxComponent = (isChecked) => {
return <CheckBox checked={isChecked ? true : false}/>
}
then in your return:
<CheckBoxComponent isChecked={yourFunctionWhereYouResolveWheneverIsOrNotChecked}/>
Another point is that you really won't to hold logic in setState callback. I guess you are a beginner. You should get better knowledge of functional programming. It's easier than handling state logic and mutation.
Judjing from your question you want probably something like that:
const yourAsyncCallToApi = async() => {
await someApiCall()
yourFunctionWhereYouResolveWheneverIsOrNotChecked() //it will be called as soon as u got data from api call
}
const yourFunctionWhereYouResolveWheneverIsOrNotChecked = () => {
// handle your conditions and return false or true based on them
}

Ramifications of React setState directly modifying prevState?

I was looking at some example react code (in the antd docs), and I noticed they have code that is equivalent to:
this.setState(prevState => { prevState.name = "NewValue"; return prevState; });
This looks a bit naughty, but does it actually break anything? Since it's using the arrow function it's not breaking the ordering of changes being applied even if React batches them up in the background.
Of course setState is intended to expect a partial state so there might be performance side effects there as it might try to apply the whole state to itself.
edit: (in response to #Sulthan)
The actual code is this:
handleChange(key, index, value) {
const { data } = this.state;
data[index][key].value = value;
this.setState({ data });
}
n.b. data is an array, so its just being copied by reference then mutated.
It's actually completely wrong as its not even using the arrow function to get the latest state.
It comes from the editable table example here: https://ant.design/components/table/
Your example can be also rewritten as:
this.setState(prevState => {
prevState.name = "NewValue"
return "NewValue";
});
When a function is passed to the state the important thing is not to mutate the passed parameter and return the new state. Your example fails both.
...prevState is a reference to the previous state. It should not be directly mutated. Instead, changes should be represented by building a new state object based on the input from prevState...
(from setState)
I am not sure whether it was ever possible to use setState like in your example but looking into the change log I really doubt it.

Resources