I am using https://reactflow.dev/ library and in Redux state when I add a new element rather than add it to the array it replaces the previous item with a new one.
Reducer
import * as types from '../actions/types';
const initialState = {
elements: [],
currentElement: undefined,
};
const flow = (state = initialState, action) => {
switch (action.type) {
case types.ADD_ELEMENT:
return {
...initialState,
elements: initialState.elements.concat(action.payload),
// elements: [...initialState.elements, action.payload],
};
}
}
Action
import { ADD_ELEMENT } from './types';
export const addElement = (node) => (dispatch) => {
dispatch({
type: ADD_ELEMENT,
payload: node,
});
};
DndFLow
const onDrop = (event) => {
event.preventDefault();
const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect();
const type = event.dataTransfer.getData('application/reactflow');
const position = reactFlowInstance.project({
x: event.clientX - reactFlowBounds.left,
y: event.clientY - reactFlowBounds.top,
});
const newNode = {
id: getId(),
type,
position,
data: { label: `${type}` },
};
addElement(newNode);
setElements((es) => es.concat(newNode));
};
You're using initialState in the reducer, instead of the state.
Using the state might fix your issue:
const flow = (state = initialState, action) => {
switch (action.type) {
case types.ADD_ELEMENT:
return {
...state,
elements: state.elements.concat(action.payload),
};
}
}
The state = initialState is correct since that means it will use the initialState by default if state doesn't have any value otherwise, but you shouldn't use the initialState beyond that, unless you want to reset your state to it.
Related
That's essentially what I want to do. The problem is this doesn't update state. Any idea what I'm missing?
type Tree = Array<Element>;
type SetLayerTreeItem = { payload: Element };
const initialState: Tree = [];
const LayersTree = createSlice({
name: 'LayersTree',
initialState,
reducers: {
setLayerTreeItem(state, { payload }: SetLayerTreeItem) {
state[payload.level] = payload; // Here's the problem
},
},
});
I ended up doing this:
setLayerTreeItem(state, { payload }: SetLayerTreeItem) {
const newState = state.slice();
newState[payload.level] = payload.element;
return newState;
},
I use REDUX in my REACTJS application. I want to retrieve the button ID after clicking the button and send it to my store. It's only works after the second click. Can you help me ? My code:
My function :
gotoidee (e) {
let test= this.state.data[e.target.id];
console.log("test" +test.titre);
const action = { type: "SAVE_IDEE_ID", value:this.state.data[e.target.id]};
this.props.dispatch(action);
console.log(this.props.ideeId.titre);
}
const mapStateToProps = (state) => {
return {
ideeId: state.saveIdee.ideeId
}
}
export default connect(mapStateToProps)(liste_idee)
My reducer :
const initialState = { ideeId: [] }
function saveIdee (state = initialState, action) {
let nextState
switch (action.type) {
case 'SAVE_IDEE_ID':
nextState = {
...state,
ideeId: action.value
}
return nextState
default:
return state
}
}
export default saveIdee
My button :
<Button type="submit" id={ideeId} onClick={this.gotoidee}>Marche</Button>
gotoidee (e) {
// check here if the click is happening by putting a console here
let test= this.state.data[e.target.id];
console.log("test" +test);
const action = { type: "SAVE_IDEE_ID", value:test};
this.props.dispatch(action);
}
render(){
console.log(this.props.ideeId); // check the updated value
}
I have a list of products called work items stored on my Redux store and I want to add an action that adds new work item or remove existing one when user picks up a a work item from the ui.
What I have so far is this workItemReducer:
import {
FETCH_WORKITEMS_BEGIN,
FETCH_WORKITEMS_SUCCESS,
FETCH_WORKITEMS_FAILURE,
SELECTED_WORKITEM
} from '../actions/workItemAction';
const initialState = {
workItems: [{"name":'work 1'}, {"name":'work 2'}, {"name":'work 3'}],
workItemsSelected: {},
loading: false,
error: null
};
export default function workItemReducer(state = initialState, action) {
switch(action.type) {
case FETCH_WORKITEMS_BEGIN:
return {
...state,
loading: true,
error: null
};
case FETCH_WORKITEMS_SUCCESS:
return {
...state,
loading: false,
workItems: action.payload.workItems
};
case FETCH_WORKITEMS_FAILURE:
return {
...state,
loading: false,
error: action.payload.error,
workItems: []
};
case SELECTED_WORKITEM:
return {
...state,
workItemsSelected: action.payload.workItem
};
default:
return state;
}
}
and the actions looks as below:
export const FETCH_WORKITEMS_BEGIN = 'FETCH_WORKITEMS_BEGIN';
export const FETCH_WORKITEMS_SUCCESS = 'FETCH_WORKITEMS_SUCCESS';
export const FETCH_WORKITEMS_FAILURE = 'FETCH_WORKITEMS_FAILURE';
export const SELECTED_WORKITEM = 'SELECTED_WORKITEM';
export const fetchWorkItemsBegin = () => ({
type: FETCH_WORKITEMS_BEGIN
});
export const fetchWorkItemsSuccess = workItems => ({
type: FETCH_WORKITEMS_SUCCESS,
payload: { workItems }
});
export const fetchWorkItemsFailure = error => ({
type: FETCH_WORKITEMS_FAILURE,
payload: { error }
});
export const selectedWorkItem = workItem => ({
type: SELECTED_WORKITEM,
payload: { workItem }
});
I have a container component that disptach or call these actions which I am a bit confused where the logic of adding a new one or removing existing one happens, either on the container/smart component or directly in the reducer.
Container component has this method:
onWorkItemSelect = (workItem) => {
this.props.dispatch(selectedWorkItem(workItem));
};
Anyone can help on writing the logic of adding new or remove existing one and where that code should live?
adding this to reducer works thou im not sure if all this code should remain into the reducer:
case SELECTED_WORKITEM:
let arr = [];
if (containsObject(action.payload.workItem, state.workItemsSelected)) {
arr = remove(state.workItemsSelected, action.payload.workItem);
} else {
arr = [...state.workItemsSelected, action.payload.workItem];
}
return {
...state,
workItemsSelected: arr
};
It should be done in the reducer
when adding one you could just spread the current array which you can get from the reducer state
const { workItems } = state;
const { workItem } = action.payload;
return {
// ...other stuff to return
workItems: [...workItems, workItem],
}
to delete one
const { workItems } = state;
const { workItem } = action.payload;
return {
// ...other stuff to return
workItems: workItems.filter(x => x.name === workItem.name),
}
I need to toggle somehove adding/removing object from redux store. It is like check/uncheck. I have following code:
const list = ({ id, item }) => {
const isChecked = name => items.some(item => item.name === name);
let itemClass = cx({
item: true,
selected: isChecked(name),
});
return (
<li className={itemClass}
onClick={() => click(fullItem)} key={id}>
<div className={styles.name}>
{isChecked(name) ?
(<span><i className={`fa fa-check`}></i>{name}</span>)
: (<span>{name}</span>)
}
</div>
</li>
);
}
export const click = item => ({
type: ADD_ITEM,
payload: item,
});
import {
ADD_ITEM,
} from "../actions";
const initialState = {
items: [],
}
export default (state = initialState, action) => {
switch (action.type) {
case ADD_ITEM:
return {
...state,
items: [action.payload],
};
default:
return state;
}
};
but for now it only work for adding item to store, when I click on item when it is selected, it should remove it from the store. How can I toggle onclick removing/adding object to redux store?
You could try something like this. Changing the ADD_ITEM instead to a TOGGLE_ITEM where you check for existence of the item using something like Array.prototype.find. Adding if it does not exist, and removing it if it does exist:
export const click = item => ({
type: TOGGLE_ITEM,
payload: item,
});
import {
TOGGLE_ITEM,
} from "../actions";
const initialState = {
items: [],
}
export default (state = initialState, action) => {
switch (action.type) {
case TOGGLE_ITEM:
const currentItem = state.items.find(item => item.id === action.payload.id);
if (!currentItem) {
return {
...state,
items: [...state.items, action.payload],
};
} else {
const newItems = state.items.filter(item => item.id !== action.payload.id];
return {
...state,
items: [...newItems]
};
}
default:
return state;
}
};
You may though want to consider having separate add, update, and delete actions, and dispatch the different actions accordingly from your components.
export const click = item => ({
type: TOGGLE_ITEM,
payload: item,
});
import {
TOGGLE_ITEM,
} from "../actions";
const initialState = {
items: [],
}
export default (state = initialState, action) => {
switch (action.type) {
case TOGGLE_ITEM:
// check to see if the item already in our array
// Array.some return true/false
const itemAlreadyExists = state.items.some(item => item.id === action.payload.id)
return {
...state,
// if the item already in our array filter it
// if not just add it
items: itemAlreadyExists
? state.items.filter(item => item.id !== action.payload.id)
: [...state.items, action.payload],
};
default:
return state;
}
};
I have a simple counter app, that increments, decrements, and maintains a total count of all clicks. Codesandbox - vanilla. I am trying to do the same thing in Redux, [Codesandbox - Redux][1]. I think the problem is how I am composing the total reducer.
actions.js
export const incrementNum = () => ({
type: constants.TOTAL,
type: constants.INCREMENT
});
export const decrementNum = () => ({
type: constants.TOTAL,
type: constants.DECREMENT
});
export const total = () => ({
type: constants.TOTAL
});
reducers.js
const decreasedNum = (state = 0, action) => {
switch (action.type) {
case constants.DECREMENT:
console.log("decrement was dispatched to decremnt reducer");
console.log(state, action);
return state - 1;
default:
return state;
}
};
// takes a user click event and returns an action to send
// to other components
const increasedNum = (state = 0, action) => {
switch (action.type) {
case constants.INCREMENT:
console.log("increment was dispatched to incremnet reducer", state);
console.log(state, action);
return state + 1;
default:
return state;
}
};
const totalNum = (state = 0, action) => {
let count = { num: 0, inc: 0, dec: 0 };
switch (action.type) {
case constants.INCREMENT:
console.log("increment was dispatched to incremnet reducer ++++", state);
//count.num = state +1;
return state + 1;
case constants.DECREMENT:
console.log("decrement was dispatched to decremnt reducer ----");
return state - 1;
case constants.TOTAL:
console.log("total is fired", state);
count.num = state + 1;
return state;
default:
return state;
}
};
container component
class CounterContainer extends Component {
constructor(props) {
super(props);
}
render() {
let number = this.props.totalNum;
number = Math.abs(this.props.totalNum) + 1;
console.log(number, this.props.totalNum, "component is getting props");
return (
<div>
<h1>{this.props.totalNum}</h1>
<div className="clicks">{this.props.totalNum}</div>
<div className="button-container">
<Decrement
decrementNum={this.props.decrementNum}
totalNum={this.props.total}
/>
<Increment
incrementNum={this.props.incrementNum}
totalNum={this.props.total}
/>
</div>
</div>
);
}
}
function mapStateToProps(state) {
return { totalNum: state.totalNum };
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ incrementNum, decrementNum, total }, dispatch);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(CounterContainer);
My original intention was to have both increment and decrement reducers passed to total, use Math.abs on decrement and add them together. Before I keep throwing crap at the wall I want to understand where I went wrong and what is the best pattern for what I'm trying to achieve.
I think the issue is you have multiple types the payload you are dispatching. Maybe something like this is what you are looking for:
actions.js:
export const incrementNum = () => {
return dispatch => {
dispatch({type: constants.TOTAL})
dispatch({type: constants.INCREMENT})
}
};
export const decrementNum = () => {
return dispatch => {
dispatch({type: constants.TOTAL})
dispatch({type: constants.DECREMENT})
}
}
export const total = () => ({
type: constants.TOTAL
});
After looking at the sandbox I see the result you are going for... So I would setup the reducers to go with the above actions like so:
reducer.js
export default handleActions({
[constants.INCREMENT]: (state) => ({...state, inc: (state.inc+1)}),
[constants.DECREMENT]: (state) => ({...state, dec: (state.dec+1)}),
[constants.TOTAL]: (state) => ({...state, total: (state.total+1)})
}, {total: 0, inc: 0, dec: 0})
This should really all be handled by the same reducer, I think you are over complicating it a little bit.
const initialState = {
actionsHandled: 0,
total: 0,
};
const increment = {
type: 'INCREMENT',
};
const decrement = {
type: 'DECREMENT',
};
const countReducer = (state = initialState, { type }) => {
switch (type) {
case 'INCREMENT':
return {
actionsHandled: state.actionsHandled + 1,
total: state.total + 1,
};
case 'DECREMENT':
return {
actionsHandled: state.actionsHandled + 1,
total: state.total - 1,
};
default:
return {
...state,
actionsHandled: state.actionsHandled + 1,
};
}
};
This way you track the number of actions you've handled/how many times it has been called, but then also allow for the inc/dec to work. You can modify multiple parts of the state in a reducer, you don't need a reducer per piece of data.