react-spring - set the initial value of an interpolation - react-spring

The following will animate from 1 to 2.
const [props, set] = useSpring(() => ({ value:1 }));
set({value: 2});
However, let's assume Iwant the value to be instantly changed to 3, and then animated to 2, regardless of what it is currently. In other words, I'd like to avoid the smooth interpolation between 1 and 3.
const [props, set] = useSpring(() => ({ value:1 }));
set({value: 3, immediate: true}); //doesn't seem to work.
set({value: 3});
How do I do so?

const [props, set] = useSpring(() => ({ value:1 }));
set({from: {value: 3}, value:2, reset: true});

Related

Why is the Redux state not updating with the API data?

I've been following the process for making an API call and storing it in global state with Redux using this project that I got from a Medium article. So far everything seems to work alright, no errors, but when I go to retrieve the global state there is nothing there. It doesn't seem to have been updated by the action that makes the API call. The relevant bits of code are as follows:
in reducers.js:
const initialState = {
mods: [],
pagination: { pageSize: 15, numPages: 1 },
sortFilter: "mostPopular",
};
const globalState = (state = initialState, action) => {
switch (action.type) {
case SET_MOD_LIST:
return { ...state, mods: state.mods };
case SET_MOD_DETAILS:
return { ...state };
default:
return state;
}
};
const rootReducer = combineReducers({
globalState,
});
export default rootReducer;
in actions.js:
export const fetchModList = (pagination, sortFilter = "mostPopular") => {
const { pageSize = 15, numPages = 1 } = pagination ?? {};
return async (dispatch) => {
const response = await fetch(
`https://www.myapi.com/mods?page=${numPages}&pageSize=${pageSize}&sortBy=${sortFilter}`
);
const resData = await response.json();
dispatch({ type: SET_MOD_LIST, mods: resData });
};
};
in index.js (Next.js root page):
const mods = useSelector((state) => state);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchModList({pageSize:2}));
}, [dispatch]);
console.log({mods})
This is 100% a result of Redux ignorance, this is my first project using it which I'm doing for an interview. Any help would be much appreciated!
Looks like you're setting mods to its own value mods: state.mods. Did you mean to set a value from action.payload rather than state.mods?

Rerender Tooltip when Scrolling React

Thanks for your help in advance, I have an issue with a tooltip, it is supposed that I should show the tooltip when a condition is given, but due to the scroll when rerendering the list the validation fails.
Here is working right, the complete list shows the tooltips where it is supposed to be. enter image description here
But then, when I scroll down the view is re-render and the tooltip fails. enter image description here
The idea is that the tooltip (and the underline) should be shown when I have group names too long using this boolean disableHoverListener={textDoesOverflow}, and it is working at the beginning but then ... fails.
Here's the code and the styles.
Please help!!
export const BaseFilteredUsersGroups: React.FC<IFilteredUsersGroups> = (props) => {
const {
userId,
filteredGroupIds = [],
localize,
} = props;
const sizeGroupsRef = React.useRef(null);
const sizeTitleRef = React.useRef(null);
const styles = useStyles();
const usersGroups = useSelector((state: IDuxStore) => {
const groups = filteredGroupIds.map(groupId => select.group.getGroupByGroupId(state, groupId));
return groups.filter(group => group?.memberships?.some(user => user.userId === userId));
});
const labelTitle = localize.formatItems(usersGroups.map(group => group.title));
const textDoesOverflow = sizeGroupsRef?.current?.getBoundingClientRect()?.width >= sizeTitleRef?.current?.getBoundingClientRect()?.width;
const finalStyle = textDoesOverflow ? styles.groupTitle : styles.groupTitleOverflow;
return (<div className={styles.usersGroups} ref={sizeGroupsRef}>
{<Tooltip title={labelTitle} disableHoverListener={textDoesOverflow} placement="top" onScrollCapture={}>
{<span className={finalStyle} ref={sizeTitleRef}>
{labelTitle}
</span>}
</Tooltip>}
</div >);
};
Here the styles:
export const useStyles = makeStyles(theme => {
return createStyles({
usersGroups:{
textOverflow: 'ellipsis',
overflow: 'hidden',
},
groupTitle: {
whiteSpace: 'nowrap',
fontWeight: theme.typography.fontWeightMedium,
color: theme.palette.text.secondary,
},
groupTitleOverflow: {
whiteSpace: 'nowrap',
fontWeight: theme.typography.fontWeightMedium,
color: theme.palette.text.secondary,
textDecorationLine: 'underline',
}
});
});
const textDoesOverflow =
sizeGroupsRef?.current?.getBoundingClientRect()?.width
>= sizeTitleRef?.current?.getBoundingClientRect()?.width;
const finalStyle = textDoesOverflow ? styles.groupTitle : styles.groupTitleOverflow;
The conditional logic here is reversed. Right now if the text width is greater than the sizeTitleRef width it will return groupTitle not groupTitleOverflow. So instead you may want to switch up the ternary operator to this:
const finalStyle = textDoesOverflow ? styles.groupTitleOverflow : styles.groupTitle;

Selector returns empty array from the redux state, even though the array has values

I have the following normalized redux state:
rootReducer: {
blocks: {
"key1": {
id: "key1",
beverages: [], // Array of objects
}
}
}
and I'm trying to select the value of beverages for beverage with the id of "key1" using this selector:
export const getBlockBeverages = (state, blockId) => {
console.log("selector", state.blocks[blockId].beverages);
return state.blocks[blockId].beverages;
};
Whenever I add a new beverage into the beverages array, the selector gets called twice, first time with an empty array, second time with proper values:
Initial state
selector []
selector []
Adding new beverage:
selector []
selector [{/*beverage1*/}]
// Adding another beverage
selector []
selector [{/*beverage1*/}, {/*beverage2*/}]
I'd really appreciate any help/explanation why does the selector get called and beverages value for the block instance is an empty array.
Below is the code for reducers I'm using - I don't see where I could be mutating the original state, I used Immer's produce from the beginning and the problem is still present. Then I tried to use lodash.clonedeep to make sure that I return a new state, but the selector still logs that empty array.
const blockReducer = (state = { id: "", beverages: [] }, action) => {
if (action.type === ADD_BEVERAGE_TO_BLOCK) {
const { beverageId } = action.payload;
const newBeverage = { id: uuid4(), beverageId };
return produce(state, (draft) => {
draft.beverages.push(newBeverage);
});
}
return state;
};
const blocks = (state = {}, action) => {
const key = action.payload.key;
if (key && (state[key] || action.type === CREATE_BLOCK)) {
const instanceState = blockReducer(state[key], action);
return produce(state, (draft: any) => {
draft[key] = instanceState;
});
}
return state;
};
Any ideas why the selector returns empty array instead of array of length 0, 1, 2 etc. as I'm adding new beverages? I'm clueless and will appreciate any help.
The problem was in a different selector that I had been using in a wrong way.
export const makeGetBlockBeveragesLength = () => createSelector(
(state, blockId) => getBlockBeverages(state, blockId),
(blockBeverages) => blockBeverages.length,
);
and instead of mapStateToProps I used createMapStateToProps:
const createMapStateToProps = (state, { blockId }) => () => {
const getBlockBeveragesLength = makeGetBlockBeveragesLength();
return {
length: getBlockBeveragesLength(state, blockId),
};
};
export const Component = connect(createMapStateToProps)(MyComponent);
The empty array logged in one of the logs refers to an older state (the initial state in this case).
I fixed the code to this and it works:
export const getBlockBeveragesLength = createSelector(
(state, blockId) => getBlockBeverages(state, blockId),
(blockBeverages) => blockBeverages.length,
);
const mapStateToProps = (state, { blockId }) => ({
length: getBlockBeveragesLength(state, blockId),
});
export const Component = connect(mapStateToProps)(MyComponent);

react-spring#next - a spring with async func as 'to' animating every render

A react-spring (version 9) spring whose 'to' value is an async funtion goes through its animation cycle every single rerender. If 'to' is a plain object, the animation triggers only initially, as expected.
Consider this component:
const Component = () => {
// let's trigger some rerenders
const [state, setState] = useState(false);
useEffect(() => {
setInterval(() => {
setState(x => !x);
}, 1000);
}, []);
// a spring with an async func provided to 'to'
const props = useSpring({
to: async (next, cancel) => {
await next({opacity: 1, color: '#ffaaee'})
await next({opacity: 0, color: 'rgb(14,26,19)'})
},
from: {opacity: 0, color: 'red'}
});
return <animated.div style={props}>I will fade in and out</animated.div>
};
The text will keep flashing forever.
I believe this is not the intended behaviour. Is this a bug, or I'm doing something wrong?
I think the intended behaviour is to show the current state of the to property of the useSpring. When it is constant then it will always show the same state at each render. But you can change the to property of the usespring. For example:
const ComponentColor = () => {
const [color, setColor] = React.useState("red");
const props = useSpring({
to: { color },
from: { color: "red" }
});
return (
<>
<animated.h2 style={props}>This color will change</animated.h2>
<button onClick={() => setColor("blue")}>blue</button>
<button onClick={() => setColor("green")}>green</button>
<button onClick={() => setColor("orange")}>orange</button>
</>
);
};
In this case the color of the text will change to the color you pressed. I think your example is in line with this one. At each render it will show the current state of the to property which is a sequence. So I think it is the intended behaviour.
If you want that useState animate only on first render. Then you can refactor the animation part to a new component and make sure, that it will only render for the first time. For example if you use React.memo it will rerender your function component only if one of its properties change. In this example there is no property so it will render only for the very first time.
const Component = () => {
// let's trigger some rerenders
const [state, setState] = React.useState(false);
React.useEffect(() => {
setInterval(() => {
setState(x => !x);
}, 2000);
}, []);
return <FirstTimeAnimate />;
};
const FirstTimeAnimate = React.memo(() => {
const props = useSpring({
to: async (next, cancel) => {
await next({ opacity: 0.25, color: "#black" });
await next({ opacity: 1, color: "blue" });
},
from: { opacity: 0, color: "red" }
});
return <animated.h2 style={props}>I will fade in and out</animated.h2>;
});
https://codesandbox.io/s/fervent-margulis-drt5l

Reselect Cannot read property 'get' of undefined

I am using reselect and react redux. I am trying to make a selector for a basic modal implementation.
my selector is
const selectModal = (state) => state.get('modal');
which throws the error of
Cannot read property 'get' of undefined
edit: It has been requested I show how I call select modal, though it should make no difference.
const mapStateToProps = createStructuredSelector({
isVisible: selectModalIsVisible(),
});
const mapDispatchToProps = {
hideModal,
showModal
};
export default connect(mapStateToProps, mapDispatchToProps)(Modal);
I believe this means the modal state container is not being found
Perhaps I am setting up my reducer or store incorrectly. My reducer is
function modalReducer(state = initialState, action) {
switch (action.type) {
case HIDE_MODAL:
return state.set(
'isVisible', false);
case SHOW_MODAL:
return state.set(
'isVisible', true);
default:
return state;
}
}
which is combined with combine reducers into a glob
export default function createReducer(asyncReducers){
return combineReducers({
route: routeReducer,
auth: authReducer,
modal: modalReducer,
...asyncReducers
});
}
and then injected into my store
function configureStore(initialState = {}, history) {
const middlewares = [
sagaMiddleware,
routerMiddleware(history),
];
const enhancers = [
applyMiddleware(...middlewares),
]
const store = createStore(
createReducer(),
fromJS(initialState),
compose(...enhancers)
);
store.runSaga = sagaMiddleware.run;
//store.close = () => store.dispatch(END)
store.runSaga(sagas);
store.asyncReducers = {};
return store;
}
var initialState = {}
const store = configureStore(fromJS(initialState), browserHistory);
The error within reselect is at lines 73/74 params = dependencies.map
var selector = function selector(state, props) {
for (var _len4 = arguments.length, args = Array(_len4 > 2 ? _len4 - 2 : 0), _key4 = 2; _key4 < _len4; _key4++) {
args[_key4 - 2] = arguments[_key4];
}
var params = dependencies.map(function (dependency) {
return dependency.apply(undefined, [state, props].concat(args));
});
return memoizedResultFunc.apply(undefined, _toConsumableArray(params));
};
So what am I doing wrong, do I need to do something with immutableJS differently to access the modal, or is my setup for the app incorrect? Thank you in advance for your feedback.
If you're using selectModal like you're using selectModalIsVisible, then your syntax is wrong. I'm pretty sure createStructuredSelector does not understand () => (state) => state.get('modal'). It would only accept (state) => state.get('modal')
Typically, my usages of createStructuredSelector will look like either
const getThing = (state, props) => state.things[props.thingId];
const getModal = state => state.get('modal');
const mapStateToProps = createStructuredSelector({
thing: getThing, // notice no parens
modal: getModal, // notice no parens
})
OR if I need selector factories:
// just pretend this selector was more complicated and needed memoization
const makeGetThing = () => createSelector(
state => state.things,
(state, props) => props.thingId,
(things, thingId) => things[thingId]);
const getModal = state => state.get('modal');
const makeMapStateToProps = () => createStructuredSelector({
thing: makeGetThing(), // yes parens
modal: getModal, // no parens
})

Resources