How to get the closest previous different prop in ReactJS - reactjs

I have a functional component with props:
function MyComponent({propA, propB, propC}) {
...
}
This component is rendered few times with different props values. I need to get the closest previous prop value which is not equal to current value. For example:
First render. propA is 1. previous value must be undefined
Second render. propA is 2. previous value must be 1
Third render. propA is 2. previous value must be 1 (not 2, because
current value is 2)
There is a similar "usePrevious" hook:
export const usePrevious = <T>(value: T): T | undefined => {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
});
return ref.current;
};
But it returns any previous value even if it has not changed. And it looks like I should modify this hook, but I don't know how
code example: https://codesandbox.io/s/cranky-snow-mkile?file=/src/index.js (props values are logged in console)

Try this:
const usePrevious = newValue => {
const refPrev = useRef()
const refCurr = useRef()
if (newValue !== refCurr.current) {
refPrev.current = refCurr.current;
refCurr.current = newValue;
}
return refPrev.current;
}

Related

Why React.Memo() keeps rendering my component

I made an example to get to know more deeply the React.Memo().
I tried to pass a string from the parent to the child. then the component wasn't rendred except the first default rerending.
But When I passed an array from the parent to the child the components get re-render.
So why the component keeps re-render when passing arrays and objects and not re-render when passing string.
Is that related to the reference values and primitive values of javascript?
here's the code for what was I trying to do.
the parent:
import Child from "./Child";
export default function App() {
const name = {
firstName: "Amr"
};
return <Child name={name} />;
}
The Child
import React from "react";
const Child = ({ name }) => {
const name1 = "Amr";
const name2 = "Amr";
// console.log(name1 === name2);
const obj1 = {
name: "Mohamed"
};
const obj2 = {
name: "Mohamed"
};
const arr1 = ["Amr"];
const arr2 = ["Amr"];
console.log(arr1 === arr2);
// console.log(obj1 === obj2); => obj1 => refrence number 1
// obj2 => refrence number 2
// 1 === 2;
// false
areEqual(name, {
firstName: "Amr"
});
return <h2>{name.firstName}</h2>;
};
// javascript [] {} => refrence value
// javascript '' or anythinng primtive value;
// obj1 = 1, obj2 = 2;
// obj1 === obj2 result false 1 === 2
function areEqual(prevProps, nextPops) {
if (prevProps === nextPops) {
console.log("equal");
} else {
console.log("not equal");
}
}
export default React.memo(Child, areEqual);
I used a function also areEqual that returns true or false based on if the prevProps and nextProps are equal or not.
This function already returns for me true when I pass a String and false when I pass an array.
So, am I right or there's something missing.
Thanks
UPDATE:
If what I mentioned is right, so the React.memo() will be useless.
Is that Right?
By default React.memo() does a shallow comparison of props and objects of props - and this is why you were experiencing rerenders when props were of type 'object', because reference recreates on each rerender.
React.memo receives two argument(2nd one is optional), and second one is custom equality function which you can use in order to stabilize rerenders when having props as non primitive values. So, for example, you can do something like this: React.memo(SomeComponent, (prevProps, currentProps) => isDeepEqual(prevProps.someObject, currentProps.someObject)).
Meaning that React.memo is not useless, it just uses shallow comparison by default, but also offers you posibillity to provide it with custom equality checker which you might wanna use in your example. Either you shall use deep equality check within React.memo, or you shall stabilize props in parent component by wrapping their value with useMemo.
It depends on their parent components some times it is necessary React.memo
But in your case, as you said, its reference changes every time you define an array or object. I recommend you to use React.useMemo. For example:
const user = React.useMemo(() => {
return {
firstName: "Amr"
}
}, []); // Second parameter works same as useCallback
React.memo wont re-render the component because user props didn't change

React usePrevious does not seem to be having previous value

In my React functional components, I am trying to use custom usePrevious to get/compare the previous value of my context object. However, it seems to be having the latest value always and not the previous value. Below is my code. Please let me know if I am doing something wrong here.
function MyHeaderComponent(props) {
const [myPref, setMyPref] = React.useContext(MyContext);
const prevPreferences = usePrevious(myPref.preferences);
useEffect(() => {
console.log("prevPreferences : " + prevPreferences); // Prints current context object, instead of the previous one
// I want to call myInfo only if prevPreferences is not the same as myPref.preferences (i.e. current)
}, [myPref]);
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
const myInfo = async () => {
setMyPref(myPref);
}
return (
<>
<div>
Some JSX
</div>
</>
)
}
export default withRouter(withStyles()(MyHeaderComponent));
function MyPreferences(props) {
const [myPref, setMyPref] = React.useContext(MyContext);
// somewhere in code
setMyPref(myPref);
}
That implementation of useLatest (which should not be within the component, by the way) does not copy the value into the ref box, it just assigns a reference.
I'd wager myPref.preferences gets internally modified (instead of being reassigned a new object), which is why you always see the same value.
I think the problem is you are using usePrevious hook inside another functional component. Could you try to create a file for the usePrevious hook.
usePreviousState.js
function usePrevious(value, initialValue) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
if (ref.current === undefined && initialValue !== undefined) {
return initialValue;
}
return ref.current;
}

Instance variable reset everytime component re-render in ReactJs

I created one functional component and declared some variables in it.
const Foo = (props) => {
let instanceVariable = null;
const selectValue = (value) => {
instanceVariable = value;
}
const proccessVariable = () => {
// Use instanceVariable and do some task
}
return(
<SelectionOption onChange={selectValue} />
);
}
I observed that whenever parent component of Foo is re-rendered or sometime Foo itself re-renders instanceVariable set back to null. Instead of this I want to save selectedValue init and proccess it later on proccessVariable() method.
If I set instanceVariable as state it will work and there is no need of state to just hold the selected value.
I know useEffect(()=>{}, []) only run one time but how can I declare instanceVariable there and use it in other functions.
Can you please let me know what I'm doing wrong.
Since you declare a variable directly in your functional component, its value is reset everytime your component re-renders. You can make use of useRef to declare instance variables in functional component
const Foo = (props) => {
let instanceVariable = useRef(null);
const selectValue = (value) => {
instanceVariable.current = value; // make sure to use instanceVariable.current
}
const proccessVariable = () => {
// Use instanceVariable.current and do some task
}
return(
<SelectionOption onChange={selectValue} />
);
}

useState() bug - state value different from initial value

I have a component that uses useState() to handle the state of its floating label, like this:
const FloatingLabelInput = props => {
const {
value = ''
} = props
const [floatingLabel, toggleFloatingLabel] = useState(value !== '')
I have a series of those components and you'd expect initialFloatingLabel and floatingLabel to always be the same, but they're not for some of them! I can see by logging the values:
const initialFloatingLabel = value !== ''
console.log(initialFloatingLabel) // false
const [floatingLabel, toggleFloatingLabel] = useState(initialFloatingLabel)
console.log(floatingLabel) // true???
And it's a consistent result. How is that possible?
How come value can be different from initialValue in the following example? Is it a sort of race condition?
const [value, setValue] = useState(initialValue)
More details here
UPDATE
This (as suggested) fixes the problem:
useEffect(() => setFloatingLabel(initialFloatingLabel), [initialFloatingLabel])
...but it creates another one: if I focus on a field, type something and then delete it until the value is an empty string, it will "unfloat" the label, like this (the label should be floating):
I didn't intend to update the floatingLabel state according to the input value at all times; the value of initialFloatingLabel was only meant to dictate the initial value of the toggle, and I'd toggle it on handleBlur and handleChange events, like this:
const handleFocus = e => {
toggleFloatingLabel(true)
}
const handleBlur = e => {
if (value === '') {
toggleFloatingLabel(false)
}
}
Is this approach wrong?
UPDATE
I keep finding new solutions to this but there's always a persisting problem and I'd say it's an issue with Formik - it seems to initially render all my input component from its render props function before the values are entirely computed from Formik's initialValues.
For example:
I added another local state which I update on the handleFocus and handleBlur:
const [isFocused, setFocused] = useState(false)
so I can then do this to prevent unfloating the label when the input is empty but focused:
useEffect(() => {
const shouldFloat = value !== '' && !isFocused
setFloatLabel(shouldFloat)
}, [value])
I'd still do this to prevent pre-populated fields from having an animation on the label from non-floating to floating (I'm using react-spring for that):
const [floatLabel, setFloatLabel] = useState(value !== '')
But I'd still get an animation on the label (from "floating" to "non-floating") on those specific fields I pointed out in the beginning of this thread, which aren't pre-populated.
Following the suggestion from the comments, I ditched the floatingLabel local state entirely and just kept the isFocused local state. That's great, I don't really need that, and I can only have this for the label animation:
const animatedProps = useSpring({
transform: isFocused || value !== '' ? 'translate3d(0,-13px,0) scale(0.66)' : 'translate3d(0,0px,0) scale(1)',
config: {
tension: 350,
},
})
The code looks cleaner now but I still have the an animation on the label when there shouldn't be (for those same specific values I mentioned at the start), because value !== '' equals to true for some obscure reason at a first render and then to false again.
Am I doing something wrong with Formik when setting the initial values for the fields?
You have the use useEffect to update your state when initialFloatingLabel change.
const initialFloatingLabel = value !== ''
const [floatingLabel, setFloatingLabel] = useState(initialFloatingLabel)
// calling the callback when initialFloatingLabel change
useEffect(() => setFloatingLabel(initialFloatingLabel), [initialFloatingLabel])
...
Your problem look like prop drilling issue. Perhaps you should store floatingLabel in a context.
// floatingLabelContext.js
import { createContext } from 'react'
export default createContext({})
// top three component
...
import { Provider as FloatingLabelProvider } from '../foo/bar/floatingLabelContext'
const Container = () => {
const [floatingLabel, setFloatingLabel] = useState(false)
return (
<FloatingLabelProvider value={{ setFloatingLabel, floatingLabel }}>
<SomeChild />
</FloatingLabel>
)
}
// FloatingLabelInput.js
import FloatingLabelContext from '../foo/bar/floatingLabelContext'
const FloatingLabelInput = () => {
const { setFloatingLabel, floatingLabel } = useContext(FloatingLabelContext)
...
}
This way you just have to use the context to change or read the floatingLabel value where you want in your components three.

What is the correct way to use react hook useState update function?

Considering the following declaration:
const [stateObject, setObjectState] = useState({
firstKey: '',
secondKey: '',
});
Are the following snippets both corrects ?
A)
setObjectState((prevState) => ({
...prevState,
secondKey: 'value',
}));
B)
setObjectState({
...stateObject,
secondKey: 'value',
}));
I am sure that A) is correct, but is it necessary ? B) seems ok, but as setObjectState is an asynchronous function, stateObject might not have the most recent value.
One useful thing about case of A that I have found is that you can use this method to update state from child components while only passing down a single prop for setObjectState. For example, say you have parent component with state you would like to update from the child component.
Parent Component:
import React, {useState} from 'react';
import ChildComponent from './ChildComponent';
export const ParentComponent = () => {
const [parentState, setParentState] = useState({
otherValue: null,
pressed: false,
});
return (
<ChildComponent setParentState={setParentState} />
)
}
Child Component:
import React from 'react';
export const ChildComponent = (props) => {
const callback = () => {
props.setParentState((prevState) => ({
...prevState
pressed: true,
}))
}
return (
<button onClick={callback}>test button<button>
)
}
When the button is pressed, you should expect to see that the state has been updated while also keeping its initial values. As for the difference between the two, there isn't much as they both accomplish the same thing.
A will always give you the updated value. B could be correct but might not. Let me give an example:
const Example = props => {
const [counter, setCounter] = useState(0);
useEffect(() => {
// 0 + 1
// In this first case the passed value would be the same as using the callback.
// This is because in this cycle nothing has updated counter before this point.
setCounter(counter + 1);
// 1 + 1
// Thanks to the callback we can get the current value
// which after the previous iexample is 1.
setCounter(latest_value => latest_value + 1);
// 0 + 1
// In this case the value will be undesired as it is using the initial
// counter value which was 0.
setCounter(counter + 1);
}, []);
return null;
};
When the new value depends on the updated one use the callback, otherwise you can simply pass the new value.
const Example = props => {
const [hero, setHero] = useState('Spiderman');
useEffect(() => {
// Fine to set the value directly as
// the new value does not depend on the previous one.
setHero('Batman');
// Using the callback here is not necessary.
setHero(previous_hero => 'Superman');
}, []);
return null;
};
Also in the example you are giving it would probably be better to use two different states:
const [firstKey, setFirstKey] = useState("");
const [secondKey, setSecondKey] = useState("");

Resources