reactjs, get state and passing it as param without changing it - reactjs

The title is no really clear but my question is pretty simple. I'm using flux pattern.
I have an event handler inside a reactclass, this handler takes an input from the user.
What I'm doing is taking this input and pass it to an action (then the action update stores and finally my state is updated).
handler(e) {
var newParams = this.state;
newParams.input = e.target.value;
MyActions.someAction(newParams);
}
As you can see I want to pass the current state completed with the new input to an action. The problem is that if I'm doing this way I'm modify directly the current state without passing by setState and it's not advised at all. I don't want to use setState in my handler I want to use setState on store change.
So my question is am I compelled to use something like underscore _.clone() or is it a simpler way to do this I haven't seen ?

In the case you want to update the state first, then execute an action, you can leverage the setState callback:
handler(e) {
this.setState({ input: e.target.value }, () => {
MyActions.someAction(this.state);
});
}
And yes, you need to create a new object, as what you are assigning is a reference. You have different ways to do so, other than _.clone:
Object.assign
Object.assign polyfill
ES7 Spread Properties

Related

setState of an empty array

I'm working on a code where I have an empty array which has to be filled after a certain request returns.
I tried to setState of the array like that:
const [recentTransactionContacts,setRecentTransactionContacts] = useState<object[]>([])
///// more code
Contacts.getAll((err, contacts) => {
if(err) throw err
if(contacts.length > 0)
setRecentTransactionContacts(prevState => ({...prevState.recentTransactionContacts, ...contacts}));
else
ToastAndroid.show(`There no contacts on your phone. Can't transfer`, ToastAndroid.SHORT)
})
Now, I don't see any change in the array of contacts after trying to do setState that way.
The only way, that I could update the state was like that:
setRecentTransactionContacts(contacts)
However, I don't think that the latter is the correct way.
What is the correct way to update the state of an array?
I think this should be served your case, the recentTransactionContacts is not changed after this line runs so you do not need to access to prevState to do what you want.
setRecentTransactionContacts([...recentTransactionContacts, ...contacts]);
Using prevState or updater function is required in cases where the current state might be changed or stale, which doesn't seem to be the case here. Infact it could be the prevState itself causing issues, as the prevState value could be undefined at run.
As stated, replace your method to
setRecentTransactionContacts([...recentTransactionContacts, ...contacts]);
which will take in the current state value and append the contacts to the state.

How can I make a function wait for another function?

I have this function:
const submitImport = async (value) => {
const { actions, navigation, account } = this.props;
this.setState({ isImporting: true });
actions.importWallet(value.mnemonicPhrase, value.password, 1);
console.log('acc', account);
actions.showNotification({
message: 'Account has been successfully Imported',
isError: false,
});
};
importWallet is adding new properties to account object but when I call this function the first time the account object is empty but when I click it the second time it is okay. So my guess is importWallet needs time to finish and return the value. I tried to use async await but it did not work. Also, I tried to use return new Promise but it still did not work. Maybe I did it the wrong way idk.
Any suggestions on how to solve this issue please?
I am assuming the importWallet function induces some change in the account prop.
All prop changes require atleast one render cycle for the updated values to get visible as state/prop changes are asynchronous in react. In your case you are trying to access account as soon invoking actions.importWallet. Hence as it is within the same render, it has not yet been updated. But as you mentioned, it will be available from the subsequent renders.
Also you cannot make it synchronous with async-await / promises as the asynchronous nature of react state updates is not exposed.
Your usecase may be achieved by some refractor :
Try obtaining the new value of account in the return statement of account.importWallet. That way you can use it even before the prop updates.
const updatedAccount = actions.importWallet(value.mnemonicPhrase, value.password, 1);
In case you are using React Hooks, you can create an effect with useEffect and add account as a dependency. This will invoke the effect when the dependency value changes (dependency should be a primitive data type for consistent effect invocation).

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

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]).

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.

onChange event for textarea instead of get value when needed

I want to understand the performance implications of React's advocated way of handling textarea's value change.
Before React's one-direction data flow philosophy, one would do:
button.onClick( processInput(textarea.value) );
Now, one has to do
textarea.onChange( dispatch({ type: "inputChange", value: textarea.value }) );
button.onClick( dispatch({ type: "buttonClick" }) );
store(
if (action.type === "inputChange") {
this.lastInput = action.value
} else if (action.type === "buttonClick") {
processInput(this.lastInput)
}
)
Is my understanding correct? Isn't this much more events compared to before? Why spam with many useless inputChange events? If my understanding is not right, what is the correct React way of doing this?
First, you're conflating a couple different things. React's "controlled input" pattern doesn't require Redux. You can implement controlled inputs inside a component, using local component state. I have a gist discussing this concept at react-controlled-inputs.md.
Second, even if you're using Redux, you don't have to directly control the input with Redux state and actions. Whether you do so is up to you. In fact, there are times when you might want to buffer text onChange events locally in the component, and only dispatch a Redux action once the user is done typing. I've got another gist demonstrating a buffering wrapper component at FormContentsContainer.jsx.
Finally, even if you do decide to directly control an input with Redux state, I'm not exactly sure why you're saying it's "more events". The input's onChange is going to fire every time you type, regardless. It's up to you whether you choose to do something with those events like translating them into Redux actions, or ignore them. Or, if you prefer to use "uncontrolled inputs" and just ask the inputs for their values when someone clicks Submit, that's entirely up to you as well.
I think it would be better to extract actual code from the Gist and put it here.
If you need text value only on button click, you can ask it through ref
const MyComponent extends Component {
onClick() {
const input = this.refs.myInput;
const value = input.value;
// do something with the value
}
render() {
return <input type="text" ref="myInput" />
}
}
You can get access to DOM element inside of your component using refs. If you need some simple solution, that will work for you.

Resources