initialValues from Redux are not updating correctly - redux-form

I have a component that looks like this,
const errorAnalysisName = { name: undefined }
const ReduxForm = (props: ReduxFormProps) => {
const { handleSubmit, children } = props
return <Form onSubmit={handleSubmit}>{children}</Form>
}
const connectedForm = connect(state => {
errorAnalysisName.name = 'insideRedux'
return {
errorAnalysisName: 'insideRedux'
}
}, null)(ReduxForm)
export default reduxForm({
form: 'CreateWizard',
enableReinitialize: true,
validate,
destroyOnUnmount: false,
initialValues: {
baseScheduleChoice: 'load',
newScheduleChoice: 'load',
currentIndex: 0,
name: errorAnalysisName.name
}
})(ReduxForm)
The field name is not updating to insideRedux, anyone see what I'm missing here?
I want to pass an Initial Value from my State, but trying to do so won't update the initialValues object in reduxForm.

If you want use use enableReinitialize you will wan't to set the initialValues prop when you render the redux form then if the prop ever changes the form will reinitialize.
I've modified your example slightly for brevity:
const CreateWizard = reduxForm({
form: 'CreateWizard',
enableReinitialize: true,
destroyOnUnmount: false
})(({ handleSubmit, children }) => (
<form onSubmit={handleSubmit}>{children}</form>
));
const Container = connect(state => ({
errorAnalysisName: state.errorAnalysisName
})(props => (
<CreateWizard initialValues={{
baseScheduleChoice: 'load',
newScheduleChoice: 'load',
currentIndex: 0,
name: props.errorAnalysisName
}} />
));
export default Container;

Related

React - Controlled input props are outdated in useCallback

I have a controlled input where its value and onChange event are set via props. When the selectedValue prop changes, onInputChanged callback gets triggered, but if I try accessing selectedValue in this callback it's out of date.
Seems like when selectedValue changes, onInputChanged gets called before it has been updated with the new dependencies
I'm probably just not understanding the lifecycle of the component correctly, but is there a way to ensure that I can access the latest selectedValue from inside the onChange event? Here's my code:
const TestInput = ({ onChange, selectedValue }) => {
const onInputChanged = React.useCallback(content => {
// Will not log the latest selectedValue
console.log(selectedValue);
onChange(content.target.value);
}, [selectedValue]);
return (
<input value={selectedValue} onChange={onInputChanged} placeholder="Test...." />
);
};
const TestContainer = () => {
const [value, setValue] = React.useState('');
return <TestInput selectedValue={value} onChange={setValue} />;
};
Heres a sandbox link to play around with - https://playcode.io/1042642
It is not possible to access it from the onChange function, since the function will not be rendered twice to show the new value, you could check it in a useEffect here documentation link.
You could try something like this:
const TestInput = ({ onChange, selectedValue }) => {
useEffect(() => {
console.log('selected value', selectedValue);
}, [selectedValue]);
const onInputChanged = React.useCallback(content => {
onChange(content.target.value);
}, [selectedValue]);
return (
<input value={selectedValue} onChange={onInputChanged} placeholder="Test...." />
);
};
const TestContainer = () => {
const [value, setValue] = React.useState('');
return <TestInput selectedValue={value} onChange={setValue} />;
};

Getting an error "A non-serializable value was detected in an action, in the path: `payload`. Value: " in react-redux

I wrote a function in add user and remove user on react-redux. But i getting error on this function
user Slice.js in redux
import { createSlice } from "#reduxjs/toolkit";
const userSlice = createSlice({
name: "users",
initialState: {
users: {
id: "",
name: "",
email: "",
},
},
reducers: {
addUser: (state, action) => {
state.users = action.payload;
},
removeUser: (state) => {
state.users = "";
},
},
});
export const { addUser, removeUser } = userSlice.actions;
export default userSlice.reducer;
The remove user funcion is returning a empty string. on the screen value is empty.
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { removeUser } from "../Slices/userSlice";
import {} from "react-redux";
const User = () => {
const users = useSelector((state) => state.users.users);
const dispatch = useDispatch();
console.log(users);
const removeuser = (state) => {
dispatch(removeUser(state));
};
return (
<div>
<h1>{users.id}</h1>
<h2>{users.name}</h2>
<h3>{users.email}</h3>
<button type="" onClick={removeuser}>
Remove User
</button>
</div>
);
};
export default User;
what the solution of this function
What I am seeing in your error message is that you are passing a React event in your action payload.
Why is this happening? An onClick prop is a function which gets called with the click event as its argument.
<button type="" onClick={removeuser}>
So the argument of removeuser needs to be (event) => {. Or if you don't need to access the event then you can use () => {. There is no scenario in which a state variable makes sense as an argument.
When you call a Redux Toolkit action creator function, the argument that you pass to the function will become the action.payload.
With the way that your slice is set up now, the removeUser reducer does not use the action.payload so it does not need an argument. You just call dispatch(removerUser()).
const onClickRemove = () => {
dispatch(removeUser());
};
/* ... */
<button onClick={onClickRemove}>
or
<button onClick={() => dispatch(removeUser())}>
Does you app have more than one user? As you begin to understand things more, your userSlice will probably evolve. You may end up with an array of users, in which case your removeUser action will need to know which user to remove.
Something like:
import { createSlice } from "#reduxjs/toolkit";
const userSlice = createSlice({
name: "users",
initialState: {
users: []
},
reducers: {
// Payload is an array of user objects.
setAllUsers: (state, action) => {
state.users = action.payload;
},
// Payload is a single user object.
addUser: (state, action) => {
state.users.push(action.payload);
},
// Payload is the id of the user to remove.
removeUser: (state, action) => {
state.users = state.users.filter(user => user.id !== action.payload);
},
},
});
export const { setAllUsers, addUser, removeUser } = userSlice.actions;
export default userSlice.reducer;
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { removeUser } from "../Slices/userSlice";
const UsersList = () => {
const users = useSelector((state) => state.users.users);
console.log(users);
return (
<div>
{users.map(user => (
<UserListItem key={user.id} user={user}/>
))}
</div>
);
}
const UserListItem = ({ user }) => {
const dispatch = useDispatch();
return (
<div>
<h1>{user.id}</h1>
<h2>{user.name}</h2>
<h3>{user.email}</h3>
<button onClick={() => dispatch(removeUser(user.id))}>
Remove User
</button>
</div>
);
};
export default UsersList;

Redux RTK reloading state when calling getSelectors

I am new to Redux RTK so the problem might not exactly be on calling getSelectors(). However, when I'm using the state that comes from getSelectors() it reloads the entire state.
Problem
The baseline is that I have different Setup objects that I'm calling based on the documentId. These Setup objects are quite large so in the getSetups I am only fetching some basic properties. Then, when the user selects a specific Setup from the dropdown I want to save it in the setupSlice. But when I trigger the dispatch(setSetup(data)) the RTK reloads all the Setups.
I encounter an infinite loop when after fetching all the Setup objects I want to automatically assign the default Setup to the setupSlice.
Extra
Ideally when I assign a Setup to the setupSlice I would like to call the getSetup from RTK to fetch the entire Setup object of that specific Setup and store it in the setupSlice.
I am not sure if this is suppose to be happening but is there anyway to stop it? Otherwise is there any recommendation so I can move forward?
This is the component I'm trying to generate:
const SetupDropdown = () => {
const dispatch = useDispatch()
const { documentId } = useParams()
const { data, isFetching } = useGetSetupsQuery({ documentId })
let setupsMenu;
const { selectAll: selectAllSetups } = getSelectors({documentId})
const allSetups = useSelector(selectAllSetups)
if (!isFetching) {
const defaultSetup = allSetups.find((setup) => setup.default)
setupsMenu = allSetups.map(setup => {
return (<MenuItem value={setup.id}>{setup.name}</MenuItem>)
})
dispatch(setSetup(defaultSetup))
}
const setupId = useSelector(selectSetupId)
const handleChange = async (event) => {
// Here I ideally call the getSetup RTK Query to fetch the entire information of the single setup
const data = {
id: event.target.value,
name: 'Random name'
}
dispatch(setSetup(data))
};
return (
<FormControl sx={{ minWidth: 200 }} size="small">
<InputLabel>Setup</InputLabel>
<Select
value={setupId}
onChange={handleChange}
label="Setup"
>
{setupsMenu}
</Select>
</FormControl>
)
}
export default SetupDropdown;
This is the setupApiSlice:
const setupsAdapter = createEntityAdapter({
sortComparer: (a, b) => b.date.localeCompare(a.date)
})
const initialState = setupsAdapter.getInitialState()
export const setupsApiSlice = apiSlice.injectEndpoints({
tagTypes: ['Setup'],
endpoints: builder => ({
getSetups: builder.query({
query: ({ documentId }) => ({
url: `/documents/${documentId}/setups`,
method: 'GET'
}),
transformResponse: responseData => {
return setupsAdapter.setAll(initialState, responseData)
},
providesTags: (result, error, arg) => [
{ type: 'Setup', id: "LIST" },
...result.ids.map(id => ({ type: 'Setup', id }))
]
}),
getSetup: builder.query({
query: ({ documentId, setupId }) => ({
url: `/documents/${documentId}/setups/${setupId}`,
method: 'GET'
})
})
})
})
export const {
useGetSetupsQuery,
useGetSetupQuery
} = setupsApiSlice
// Define function to get selectors based on arguments (query) of getSetups
export const getSelectors = (
query,
) => {
const selectSetupsResult = setupsApiSlice.endpoints.getSetups.select(query)
const adapterSelectors = createSelector(
selectSetupsResult,
(result) => setupsAdapter.getSelectors(() => result?.data ?? initialState)
)
return {
selectAll: createSelector(adapterSelectors, (s) =>
s.selectAll(undefined)
),
selectEntities: createSelector(adapterSelectors, (s) =>
s.selectEntities(undefined)
),
selectIds: createSelector(adapterSelectors, (s) =>
s.selectIds(undefined)
),
selectTotal: createSelector(adapterSelectors, (s) =>
s.selectTotal(undefined)
),
selectById: (id) => createSelector(adapterSelectors, (s) =>
s.selectById(s, id)
),
}
}
This is the setupSplice:
const initialState = {
name: null,
filters: [],
data: {},
status: 'idle', //'idle' | 'loading' | 'succeeded' | 'failed'
error: null
}
const setupSlice = createSlice({
name: 'setup',
initialState,
reducers: {
setSetup: (state, action) => {
console.log('Dispatch')
const setup = action.payload;
console.log(setup)
state.id = setup.id;
state.name = setup.name;
state.filters = setup.filters;
state.data = setup.state;
state.status = 'succeeded';
}
}
})
export const { setSetup } = setupSlice.actions;
export const selectSetupId = (state) => state.setup.id;
export const selectSetupName = (state) => state.setup.name;
export const selectSetupFilters = (state) => state.setup.filters;
export const selectSetupData = (state) => state.setup.data;
export default setupSlice.reducer;
Tbh., you probably should be using selectFromResult in your useGetSetupsQuery instead of adding another useSelector hook. That would also reduce your code complexity by a lot.
Your problem as hand is that you are creating those selectors within your component on each render - so they don't have a chance to actually memoize and give you a stable result. If you do that in your component, wrap it in a useMemo call to keep your selector instances as stable as possible.

selector returning different value despite custom equalityFn check returning true

I have the following selector and effect
const filterValues = useSelector<State, string[]>(
state => state.filters.filter(f => f.field === variableId).map(f => f.value),
(left, right) => {
return left.length === right.length && left.every(l => right.includes(l));
},
);
const [value, setValue] = useState<SelectionRange>({ start: null, end: null });
useEffect(() => {
const values = filterValues
.filter(av => av).sort((v1, v2) => v1.localeCompare(v2));
const newValue = {
start: values[0] ?? null,
end: values[1] ?? null,
};
setValue(newValue);
}, [filterValues]);
the selector above initially returns an empty array, but a different one every time and I don't understand why because the equality function should guarantee it doesn't.
That makes the effect trigger, sets the state, the selector runs again (normal) but returns another different empty array! causing the code to run in an endless cycle.
Why is the selector returning a different array each time? what am I missing?
I am using react-redux 7.2.2
react-redux e-runs the selector if the selector is a new reference, because it assumes the code could have changed what it's selecting entirely
https://github.com/reduxjs/react-redux/issues/1654
one solution is to memoize the selector function
const selector = useMemo(() => (state: State) => state.filters.filter(f => f.field === variableId).map(f => f.value), [variableId]);
const filterValues = useSelector<State, string[]>(
selector ,
(left, right) => {
return left.length === right.length && left.every(l => right.includes(l));
},
);
You can try memoizing the result of your filter in a selector and calculate value in a selector as well, now I'm not sure if you still need the local state of value as it's just a copy of a derived value from redux state and only causes an extra render when you copy it but here is the code:
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { createSelector, defaultMemoize } = Reselect;
const { useState, useEffect, useMemo } = React;
const initialState = {
filters: [
{ field: 1, value: 1 },
{ field: 2, value: 2 },
{ field: 1, value: 3 },
{ field: 2, value: 4 },
],
};
//action types
const TOGGLE = 'NEW_STATE';
const NONE = 'NONE';
//action creators
const toggle = () => ({
type: TOGGLE,
});
const none = () => ({ type: NONE });
const reducer = (state, { type }) => {
if (type === TOGGLE) {
return {
filters: state.filters.map((f) =>
f.field === 1
? { ...f, field: 2 }
: { ...f, field: 1 }
),
};
}
if (type === NONE) {
//create filters again should re run selector
// but not re render
return {
filters: [...state.filters],
};
}
return state;
};
//selectors
const selectFilters = (state) => state.filters;
const createSelectByVariableId = (variableId) => {
const memoArray = defaultMemoize((...args) => args);
return createSelector([selectFilters], (filters) =>
memoArray.apply(
null,
filters
.filter((f) => f.field === variableId)
.map((f) => f.value)
)
);
};
const createSelectSelectValue = (variableId) =>
createSelector(
[createSelectByVariableId(variableId)],
//?? does not work in SO because babel is too old
(values) => ({
start: values[0] || null,
end: values[1] || null,
})
);
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(() => (next) => (action) =>
next(action)
)
)
);
var last;
const App = ({ variableId }) => {
const selectValue = useMemo(
() => createSelectSelectValue(variableId),
[variableId]
);
const reduxValue = useSelector(selectValue);
if (last !== reduxValue) {
console.log('not same', last, reduxValue);
last = reduxValue;
}
//not sure if you still need this, you are just
// copying a value you already have
const [value, setValue] = useState(reduxValue);
const dispatch = useDispatch();
useEffect(() => setValue(reduxValue), [reduxValue]);
console.log('rendering...', value);
return (
<div>
<button onClick={() => dispatch(toggle())}>
toggle
</button>
<button onClick={() => dispatch(none())}>none</button>
<pre>{JSON.stringify(value, undefined, 2)}</pre>
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App variableId={1} />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<div id="root"></div>

Set value of react-widgets DropdownList from redux store

I'm trying to connect the react-widgets DropdownList component to global redux store.
The DropdownList changes the value of the selected value if it is obtained from the this.state.
In the sample from the react-widgets docs in the onChange handler:
var DropdownList = ReactWidgets.DropdownList
, colors = ['orange', 'red', 'blue', 'purple'];
var Example = React.createClass({
getInitialState() {
return { value: colors[3] };
},
render() {
return (
<DropdownList
data={colors}
value={this.state.value}
onChange={value => this.setState({ value })}/>)
}
});
ReactDOM.render(<Example/>, mountNode);
But I want to set DropdownList value from the global redux store my codepen example:
const colors = [
{ id: 0, name: 'orange'},
{ id: 1, name: 'purple'},
{ id: 2, name: 'red' },
{ id: 3, name: 'blue' },
{ id: 4, name: 'green' }
];
class App extends React.Component {
componentWillMount() {
this.props.setSelectedColor(colors[1]); // !!! HELP, does not work !!!
}
render() {
return(
<DropdownList className="form-control form-control-sm"
id="color-list"
valueField="id"
textField="name"
data={colors}
value={this.props.selectedColor}
onChange={(value) => this.props.setSelectedColor(value)}
/>);
}
}
const reducers = {
list: setSelectedColor
};
// global redux state <-> store
const store = createStore(rootReducer);
// connect to global redux store selectedColor
function mapStateToProps (state) {
return {
selectedColor: state.selectedColor
}
}
// connect to global actions (setColor) to redux store
function mapDispatchToProps(dispatch) {
return {
setSelectedColor: bindActionCreators(setColor, dispatch)
}
}
const AppForm = connect(
mapStateToProps,
mapDispatchToProps
)(App);
ReactDOM.render(
<Provider store={store}>
<AppForm />
</Provider>,
document.getElementById('app')
);
In the method componentWillMount() I set the initial value list in the second element (purple) but it does not work!
What am I doing wrong?
I corrected the path connection state through reducer "list":
const reducers = {
list: setSelectedColor
};
function mapStateToProps (state) {
return {
selectedColor: state.list.selectedColor
}
}
Hooray, now it works!

Resources