I have initial state like this:
const initialState = {
array: [
{
key: "value",
obj: {
key1: "value",
key2: "value",
},
array: [
{
key: "value",
obj: {
key1: "value",
key2: "value",
},
}
]
},
{
key: "value",
obj: {
key1: "value",
key2: "value",
},
},
{
key: "value",
obj: {
key1: "value",
key2: "value",
},
},
],
path: "",
value: ""
};
Reducer:
export const reducer = (state = initialState, action) => {
switch (action.type) {
case "SET_PATH":
return {
...state,
path: action.path
};
case "SET_NEW_VALUE":
return {
...state,
newValue: action.value
};
case "SET_NEW_BUILD":
//What next?
default:
return state
}
};
Action creators:
const setPath = (path) => ({type: "SET_PATH", path});
const setNewValue = (value) => ({type: "SET_NEW_VALUE", value});
const setNewBuild = (path, value) => ({type: "SET_NEW_BUILD", path, value});
And i need to change this state after this dispatch using a path string and new value.
dispatch(setNewBuild("array[0].obj.key1", "newValue");
Also the value can have form like this "obj: {key1: "newValue", key2: "newValue"}" hence will be created a new object.
How can i do this?
Here is an example using the set helper:
const REMOVE = () => REMOVE;
//helper to get state values
const get = (object, path, defaultValue) => {
const recur = (current, path, defaultValue) => {
if (current === undefined) {
return defaultValue;
}
if (path.length === 0) {
return current;
}
return recur(
current[path[0]],
path.slice(1),
defaultValue
);
};
return recur(object, path, defaultValue);
};
//helper to set state values
const set = (object, path, callback) => {
const setKey = (current, key, value) => {
if (Array.isArray(current)) {
return value === REMOVE
? current.filter((_, i) => key !== i)
: current.map((c, i) => (i === key ? value : c));
}
return value === REMOVE
? Object.entries(current).reduce((result, [k, v]) => {
if (k !== key) {
result[k] = v;
}
return result;
}, {})
: { ...current, [key]: value };
};
const recur = (current, path, newValue) => {
if (path.length === 1) {
return setKey(current, path[0], newValue);
}
return setKey(
current,
path[0],
recur(current[path[0]], path.slice(1), newValue)
);
};
const oldValue = get(object, path);
const newValue = callback(oldValue);
if (oldValue === newValue) {
return object;
}
return recur(object, path, newValue);
};
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore } = Redux;
//action
const setNewBuild = (path, value) => ({
type: 'SET_NEW_BUILD',
path,
value,
});
const initialState = {
array: [
{
key: 'value',
obj: {
key1: 'value',
key2: 'value',
},
},
],
path: '',
value: '',
};
const reducer = (state = initialState, action) => {
const { type } = action;
if (type === 'SET_NEW_BUILD') {
const { path, value } = action;
return set(state, path, () => value);
}
return state;
};
const store = createStore(
reducer,
initialState,
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__()
);
const App = () => {
const state = useSelector((x) => x);
const dispatch = useDispatch();
return (
<div>
<button
onClick={() =>
dispatch(
setNewBuild(
['array', 0, 'obj', 'key1'],
'new value key 1'
)
)
}
>
change array 0 obj key1
</button>
<button
onClick={() =>
dispatch(
setNewBuild(['array', 0, 'obj'], {
key1: 'change both key1',
key2: 'change both key2',
})
)
}
>
change array 0 obj
</button>
<pre>{JSON.stringify(state, undefined, 2)}</pre>
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</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>
The important bits are:
<button
onClick={() =>
dispatch(
// path is an array
setNewBuild(['array', 0, 'obj'], {
key1: 'change both key1',
key2: 'change both key2',
})
)
}
>
change array 0 obj
</button>
And in the reducer:
const { type } = action;
if (type === 'SET_NEW_BUILD') {
const { path, value } = action;
return set(state, path, () => value);
}