I'm trying to animate View with interpolate. I'd like to get a current value of my Animated.Value, but don't know how. I didn't understand how to do it with React-native docs.
this.state = {
translateAnim: new Animated.Value(0)
}
DeviceEventEmitter.addListener('Accelerometer', function (data) {
console.log(this.state.translateAnim);
// returns an object, but I need a value in current moment
}
I find out, how to get a value:
this.state.translateAnim.addListener(({value}) => this._value = value);
EDIT
to log a value I do the following:
console.log(this.state.translateAnim._value)
This also works for me...
const headerHeight = new Animated.Value(0);
After some manipulation....
console.log(headerHeight.__getValue())
It feels hackish but it gets the job done...
For the people with typescript.
console.log((this.state.translateAnim as any)._value);
It worked for me to full tsc as any.
Number.parseInt(JSON.stringify(translateAnim))
It works on React Hook
edit: CAUTION - MAY CAUSE SEVERE PERFORMANCE ISSUES. I have not been able to figure out why, but if you use this for 30+ simultaneous animations your framerate will slow to a crawl. It seems like it must be a bug in react-native with Animated.Value addListener as I don't see anything wrong with my code, it only sets a listener which sets a ref which should be instantaneous.
Here's a hook I came up with to do it without resorting to accessing private internal values.
/**
* Since there's no (official) way to read an Animated.Value synchronously this is the best solution I could come up with
* to have access to an up-to-date copy of the latest value without sacrificing performance.
*
* #param animatedValue the Animated.Value to track
* #param initial Optional initial value if you know it to initialize the latest value ref before the animated value listener fires for the first time
*
* returns a ref with the latest value of the Animated.Value and a boolean ref indicating if a value has been received yet
*/
const useAnimatedLatestValueRef = (animatedValue: Animated.Value, initial?: number) => {
//If we're given an initial value then we can pretend we've received a value from the listener already
const latestValueRef = useRef(initial ?? 0)
const initialized = useRef(typeof initial == "number")
useEffect(() => {
const id = animatedValue.addListener((v) => {
//Store the latest animated value
latestValueRef.current = v.value
//Indicate that we've recieved a value
initialized.current = true
})
//Return a deregister function to clean up
return () => animatedValue.removeListener(id)
//Note that the behavior here isn't 100% correct if the animatedValue changes -- the returned ref
//may refer to the previous animatedValue's latest value until the new listener returns a value
}, [animatedValue])
return [latestValueRef, initialized] as const
}
It seems like private property. But works for me. Helpful for debugging, but wouldn't recommend using it in production.
translateAnim._value
I actually found another way to get the value (not sure if it is a recommended way, but it works).
Use JSON.stringify() on the animated value and use Number on the result to convert it into Number.
E.g,
const animatedVal = new Animated.Value(0);
const jsanimated = JSON.stringify(animatedVal);
const finalResult = Number(jsanimated)
Related
In my use case I have an array of characters, each character has multiple builds, and each build has a weapons string, and artifacts string. I'm making a tool to select portions of each string and assign them to a value, e.g. assigning index 3-49 of weapons to a specific weapon.
const [characterIndices, setCharacterIndices] = useState<
{ builds: { weaponIndices: SE[]; artifactSetIndices: SE[] }[] }[]
>([
...characters.map((char) => {
return {
builds: [
...char.builds.map((_build) => {
return {
weaponIndices: [],
artifactSetIndices: [],
};
}),
],
};
}),
]);
The SE type is as follows:
type SE = { start: number; end: number; code: string };
//start and end are the respective start and end of selected text
//code is the specific artifact or weapon
The weaponIndices and artifactSetIndices basically hold the start and end of selected text in a readonly textarea.
I have a function to add a SE to either weaponIndices or artifactSetIndices:
const addSE = (
type: "weaponIndices" | "artifactSetIndices",
{ start, end, code }: SE,
characterIndex: number,
buildIndex: number
) => {
let chars = characterIndices;
chars[characterIndex].builds[buildIndex][type].push({ start, end, code });
setCharacterIndices((_prev) => chars);
console.log(characterIndices[characterIndex].builds[buildIndex][type]);
};
I think that using a console log after using a set function isn't recommended, but it does show what it's intended to the weaponIndices, or artifactSetIndices after an entry is added.
Passing the addSE function alongside characterIndices to a separate component, and using addSE, does print the respective indices after adding an entry, but the component's rendering isn't updated.
It only shows up when I "soft reload" the page, when updating the files during the create-react-app live reload via npm run start.
In case you are confused about what the data types are, I've made a github repo, at https://github.com/ChrisMGeo/ght-indexer/tree/main/src at src/data.json. That JSON file describes what the character data looks like, including the builds, and each build's weapons and artifacts(called artifact_sets in the JSON)
Looks to me you are not updating the state at all.
Here you are just storing the same object reference that you already have in state into a new variable chars.
let chars = characterIndices;
chars now holds reference to a same object as characterIndices.
Here you are mutating that same object
chars[characterIndex].builds[buildIndex][type].push({ start, end, code });
And here you are updating the state to the same object that is already in the state. Notice that no state update here occurs.
setCharacterIndices((_prev) => chars);
Object you have in state is mutated, but you did not "change" the value of the state, thus no component re-render.
What you could maybe do is create a copy of the object, mutate that and update the state. just change chars assignment like this:
let chars = {...characterIndices};
React often compares values using Object.is() only to a single level of nesting (the tested object and its children).
It will not re-render if the parent is found equal, or if all the children are found equal.
React then considers that nothing has changed.
In your implementation, even the first top-level check will immediately fail, since Object.is(before, after) will return true.
You could use an Immutable objects approach to eliminate this concern when setting a new state (either directly through spreading values or with a support library such as Immer).
For example instead of setting the values within the object...
myObj.key = newChildObj
...you would make a new object, which preserves many of the previous values.
myObj === {...myObj, key: newChildObj}
This means that every changed object tree is actually a different object (with only the bits that haven't changed being preserved).
To read more about this see https://javascript.plainenglish.io/the-effect-of-shallow-equality-in-react-85ae0287960c
when using React.useState, I can't mutate the object before using the setState method.
for example:
const [buffer, setBuffer] = React.useState(props.someObject);
function handleChange(field: string, data: someObject) {
const update = lodash.set(buffer, field, data);
setBuffer(update);
}
const update never gets the updated data at the path of field. If field is something like "path/path/value" just using setBuffer isn't simple without the help of lodash.set
I know I can do const update = lodash.set(lodash.cloneDeep(buffer), field, data); but that is expensive, especially when we're talking about handleChange being called on every user interaction with a form.
Any alternative approaches or insights into why updates on React.useState objects don't work is much appreciated!
I have a react component with an AgGridReact which has an editable column. I want inside the onCellValueChanged event to check if the new value meets some conditions, and if not stop and restore the old value.
I've read that there is an api stopEditing:
https://www.ag-grid.com/documentation/javascript/cell-editing/#editing-api
Here it says:
"stopEditing(cancel): If the grid is editing then editing is stopped. Passing cancel=true will keep the cell's original value and passing cancel=false will take the latest value from the cell editor."
So I've tried using it. Just to test it out I did like this:
const onGridReady = params =>
{
gridApi.current = params.api;
}
const onCellValueChanged = async (event) =>
{
gridApi.current.stopEditing(true);
return;
}
I expected that every time I'll try to edit, no matter what, as soon as I click enter to finish editing, the old value will be restored but it just keeps the new value. The event itself is set properly and it is being invoked.
What am I doing wrong here? And if it's not the way to restore the previous value, what is?
stopEditing is called on the gridApi. By calling stopEditing in your onCellValueChanged, it's too late, the value has already changed.
Here's what you can do instead:
Use a valueSetter for your column. This way, you can accept only certain values and reject all others. For example, this valueSetter will only update the value if the newly entered value is "accept":
function myValuSetter(params) {
if (params.newValue != "accept") {
return false;
}
params.data.make = params.newValue
return true;
}
Demo.
I'm trying to add and remove upload gifs for a filtering event
Where did I fail?
#observable loading = false; //Loading variable being tracked in component
#computed get filtered() {
this.loading=true;//true
let filteredList = this.list.filter(
item => item.data.some(
obj => obj.tr_x.toLowerCase().includes(this.filterTermValue)
)
);
this.loading=false;//false
if (filteredList.length)
return filteredList;
return this.list;
}
What is a #computed decoration?
It is a kind of cache. If the arguments does not change, do not compute, just return the previous computed value.
So as good practice to avoid side effects those #computed functions can not change anything, just compute idempotent information.
And you need to look to your stack call to ensure the code that change any property are not called by a #computed function.
Since the React Relay createPaginationContainer does not support offset-based pagination, the next best option would be to handle this feature through the use of the createRefetchContainer.
In the example provided on the Relay Modern documentation https://facebook.github.io/relay/docs/refetch-container.html, when implemented will paginate forward one time, but only because we are transitioning from our default state at offset of 0 to our new state of 0 + 10. Subsequent click events produce the same result since the values are not being stored.
I would have expected that the offset value would continue to increment but it does not appear that the state is being maintained through each refetch.
I came across this issue on the repo which seems to have addressed this, https://github.com/facebook/relay/issues/1695. If this is the case then the documentation has not been updated.
While I believe there should be a built in mechanism for this, I ultimately ended up storing values in state and using callbacks to trigger my refetch.
So in the example above which I listed from the documentation the update appears to happen here:
_loadMore() {
// Increments the number of stories being rendered by 10.
const refetchVariables = fragmentVariables => ({
count: fragmentVariables.count + 10,
});
this.props.relay.refetch(refetchVariables, null);
}
So the issue I have with this particular example is that we are pulling the default state from the fragmentVariable so in essence no real change is ever occurring. This may be acceptable depending on your implementation but I feel that for most use cases we would like to see values being actually updated as variables in the updated fragment.
So the way I approached this in terms of my offset-based pagination was...
_nextPage = () => {
if ((this.state.offset + this.state.limit) < (this.state.total - this.state.limit) {
this.setState({ offset: (this.state.offset + this.state.limit), () => {
this._loadMore();
}
}
}
_loadMore = () => {
const refetchVariables = {
offset: this.state.offset,
limit: this.state.limit
}
this.props.relay.refetch(refetchVariables, null);
}
May have a typo, I'm not actually looking at my code right now. But by using the state of the component, you will effectively be able to update the variables of the refetchContainer.