Redux + reactflow add edges through a reducer - reactjs

Hi I'm still new to redux and I'm trying to manipulate the nodes in reactflow library using redux, I've managed to add a node through a reducer but when I try to add an edge(the link between nodes) it returns a warning
can anyone helps me with how to handle it in reducer
Link to sandbox if you want to see the code
https://codesandbox.io/s/react-flow-add-node-button-with-redux-1q2dz?file=/src/store/reducer.js
warning image

This question is more a react-flow question than it is a redux question but the issue is that you aren't dispatching an action with the source and target node ids you want to connect.
App: dispatch the add edge action with the vertices/node ids you want to connect:
const AddEdge = ({ source, target }) => {
dispatch({
type: "ADD_EDGE",
source,
target,
});
};
Reducer - don't mutate the state object, just return a new state object value:
export const reducer = (state = initialElements, action) => {
switch (action.type) {
case ADD_NODE:
return [
...state,
{
id: Math.random().toString(),
position: { x: 100, y: 50 },
data: { label: "yo" }
}
];
case ADD_EDGE:
return [
...state,
{
id: Math.random().toString(),
source: action.source,
target: action.target
}
];
default:
return state;
}
};

Related

How to properly update the reducer

In my initial state I have boards, think of them as groups in a chat room.
setboard is a variable use to switch between rooms using
activeBoard. =>
activeBoard: state.boards[initialState.setBoard],
I map the content of debates easily. The problem comes when I try to update the reducer.
const initialState = {
setBoard: 'Feed',
boards : {
Feed: {
id: 1,
debates: [
{
id: 1,
text: 'This is the most amazing website on the internet.",
Images: 'soul.jpg',
},
{
id: 2,
topic: 'Somebody tell him to shut up',
text: "This is the most amazing website on the internet.",
Images: 'salt.jpg',
},
],
invitations: [
{
id: 1,
nickname: "imhotep",
inviteText: ' BLOCKING People block or unfriend their parents on Facebook
},
],
}
export const BoardProvider = ({ children}) =>{
const [state, dispatch] = useReducer(BoardReducer, initialState)
function AddDebates(debates){
dispatch({
type: 'Add-debates',
payload: debates
})
}
return ( <BoardContext.Provider value={{
boards: state.boards,
activeBoard: state.boards[initialState.setBoard],
debates: activeBoard.debates,
AddDebates
}}>
{children}
</BoardContext.Provider>)
}
This is my reducer.
export default ( state, action, activeBoard, debates,invitations) =>{
switch(action.type) {
case 'Add-debates':
return {
...state,
debates: [action.payload, ...debates]
}
default:
return state
}
}
I get an error: TypeError: debates is not iterable
I can render debates by simply mapping it but can update reducer this way. Some help pls...
You need to shallowly copy all levels of state from root to debates that you are updating as it is nested a few levels deep. The correct reference will also include the full path to that property, i.e. state.debates is undefined and not iterable.
case 'Add-debates':
return {
...state,
boards: {
...state.boards,
Feed: {
...state.boards.Feed,
debates: [action.payload, ...state.boards.Feed.debates],
},
},
}

Problem with Reducer that contains few different values

I'm kind of new to React.js & Redux, so I have encountered a problem with Reducers.
I am creating a site that have a main "Articles" page, "Question & Answers" page, I created for each one a separate Reducer that both work just fine.
The problem is in "Main Page" which contains a lot of small different pieces of information, and I don't want to create each little different piece of information its on Reducer, so I am trying to create one Reducer which will handle a lot of very small different pieces of information, and I can't make that work, inside the main "Content" object, I put 2 Key Value Pairs that each have an array, one for each different information, one is "Features" info, and one for the "Header" info.
This is the error that I'm getting:
Uncaught TypeError: Cannot read property 'headerContent' of undefined
at push../src/reducers/ContentReducer.js.__webpack_exports__.default (ContentReducer.js:15)
I am not sure what's the problem, maybe my code is wrong or maybe my use of the spread operator, any solution?
I have added the necessary pages from my code:
ACTIONS FILE
export const addFeatureAction = (
{
title = 'Default feature title',
feature = 'Default feature',
} = {}) => ({
type: 'ADD_FEATURE',
features: {
id: uuid(),
title,
feature
}
})
export const addHeaderAction = (
{
title = 'Default header title',
head = 'Default header',
} = {}) => ({
type: 'ADD_HEADER',
header: {
id: uuid(),
title,
head
}
})
REDUCER FILE:
const defaultContentReducer = {
content: {
featuresContent: [],
headerContent: [],
}
}
export default (state = defaultContentReducer, action) => {
switch(action.type) {
case 'ADD_FEATURE':
return [
...state.content.featuresContent,
action.features
]
case 'ADD_HEADER':
return [
...state.content.headerContent,
action.header
]
default:
return state
}
}
STORE FILE:
export default () => {
const store = createStore(
combineReducers({
articles: ArticleReducer,
qnaList: QnaReducer,
content: ContentReducer
})
);
return store;
}
The reducer function is supposed to return the next state of your application, but you are doing a few things wrong here, you are returning an array, a piece of the state and not the state object, I would suggest you look into immer to prevent this sort of errors.
Simple fix:
export default (state = defaultContentReducer, action) => {
switch(action.type) {
case 'ADD_FEATURE':
return {...state, content: {...state.content. featuresContent: [...action.features, ...state.content.featuresContent]}}
// More actions are handled here
default:
return state
}
}
If you use immer, you should have something like this
export default (state = defaultContentReducer, action) => {
const nextState = produce(state, draftState => {
switch(action.type) {
case 'ADD_FEATURE':
draftState.content.featuresContent = [...draftState.content.featuresContent, ...action.features]
});
break;
default:
break;
return nextState
}

Modifying state for specific item in array in redux reducer

I can't quite wrap my head around the boilerplate of redux. I looked up common patterns for immutable modifying of state but issue is, all these patterns simply push towards the end and not for a specific index.
Before I'll go into actual code, here's what the structure of the state looks like for better imagination (pseudo-code):
state = {
quizMenu: {...},
quizEditor: Array<Question>,
> type Question = {
id: number,
question: string,
questionOptions: Array<QuestionOption>,
}
> type QuestionOption = {
id: number,
optionText: string,
isValid: boolean,
}
}
Hopefully it makes sense. I have created an action for adding questions, which works fine. Now I'm trying to create an action for adding option to an already existing question, but I can't wrap my head around how to in the nested arrays of objects.
Here's how my action in question is defined:
const AQO = 'ADD_QUESTION_OPTION';
/*
* #param questionId - ID of the question we're accesssing in quizEditor array
* #param id - id of the option we're adding (handled in component)
*/
const actionAddQuestionOption = createAction(
AQO,
(questionId: number, id: number) => ({
payload: {
id,
optionText: 'New option',
isValid: false,
questionId,
},
})
);
Now my reducer is the following way:
const reducer = createReducer({//...}, {
[actionAddQuestionOption.type]: (state, action) => ({
...state,
quizEditor: [...state.quizEditor][action.payload.questionId].questionOptions.push({
id: action.payload.id,
optionText: action.payload.optionText,
isValid: action.payload.isValid,
})
})
}
This just ends up in this monster type-error: https://pastebin.com/raw/pBbnxcQp
But I'm pretty sure I'm accessing the Array inside the array of objects incorrectly.
quizEditor: [...state.quizEditor][action.payload.questionId].questionOptions
Does anyone know what would be the proper way of going about accessing it? Much appreciated!
Since you are using redux-toolkit which has immer built in you can just mutate the state directly and it will transform it into an immutable update internally
const reducer = createReducer({
[actionAddQuestionOption.type]: (state, { payload: { questionId, ...option }}) => {
const question = state.questionquizEditor(question => question.id === questionId)
question.questionOptions.push(option)
}
})
The way to make it an immuable update is like this
const reducer = createReducer({
[actionAddQuestionOption.type]: (state, { payload: { questionId, ...option } }) => ({
...state,
quizEditor: state.quizEditor.map(question =>
(question.id === questionId
? {
...question,
questionOptions: [...question.questionOptions, option],
}
: question)),
}),
})
the push method of Array returns the new length of the array not the array itself. What you can do is just concat the new object to the array which in turn will return the new array with the new question option.
[...state.quizEditor][action.payload.questionId].questionOptions.concat({
id: action.payload.id,
optionText: action.payload.optionText,
isValid: action.payload.isValid,
})
Furthermore, we have to modify only that property in the state with our new array:
const reducer = createReducer({
//...}, {
[actionAddQuestionOption.type]: (state, action) => {
const quizEditor = [...state.quizEditor];
quizEditor[action.payload.questionId].questionOptions = quizEditor[
action.payload.questionId
].questionOptions.concat({
id: action.payload.id,
optionText: action.payload.optionText,
isValid: action.payload.isValid
});
return {
...state,
quizEditor
};
}
});
Thanks to immer in redux toolkit we can make it more readable:
const reducer = createReducer({
//...}, {
[actionAddQuestionOption.type]: (state, action) => {
const question = state.quizEditor[action.payload.questionId];
question.questionOptions = [
...question.questionOptions,
{
id: action.payload.id,
optionText: action.payload.optionText,
isValid: action.payload.isValid
}
];
return state;
}
});

Delete an element from array using redux

I am trying to make a todo app using redux and I'm stack on how to delete a todo from the array.
reducer.js
export default function todo(state, action) {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
id: action.id,
text: action.text,
completed: false
}
case 'REMOVE_TODO':
return {
id: action.id,
...state.slice(id, 1)
}
default:
return state;
}
}
action.js
let nextTodoId = 0
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text
})
export const removeTodo = id => {
type: 'REMOVE_TODO',
id
}
So far i can add and toggle a todo as completed or not. Thanks
Using redux you need to return all array elements except the removed one from reducer.
Personally, I prefer using the filter method of the Array. It'll return you a shallow copy of state array that matches particular condition.
case 'REMOVE_TODO':
return state.filter(({id}) => id !== action.id);
In react redux application, you should know, you always have to create a new object,
to deleting an item please use spread operator like this :
return [...state.filter(a=>a.id !== id)]

Updating state in reducer using variables

I'm build a simple app that expands and collapses sections of content based on their state. Basically, if collapse = false, add a class and if it's true, add a different class.
I'm using Next.js with Redux and running into an issue. I'd like to update the state based on an argument the action is passed. It's not updating the state and I'm not sure why or what the better alternative would be. Any clarification would be great!
// DEFAULT STATE
const defaultState = {
membership: 'none',
sectionMembership: {
id: 1,
currentName: 'Membership',
nextName: 'General',
collapse: false
},
sectionGeneral: {
id: 2,
prevName: 'Membership',
currentName: 'General',
nextName: 'Royalties',
collapse: true
}
}
// ACTION TYPES
export const actionTypes = {
SET_MEMBERSHIP: 'SET_MEMBERSHIP',
MOVE_FORWARDS: 'MOVE_FORWARDS',
MOVE_BACKWARDS: 'MOVE_BACKWARDS'
}
// ACTION
export const moveForwards = (currentSection) => dispatch => {
return dispatch({ type: actionTypes.MOVE_FORWARDS, currentSection })
}
// REDUCERS
export const reducer = (state = defaultState, action) => {
switch (action.type) {
case actionTypes.SET_MEMBERSHIP:
return Object.assign({}, state, {
membership: action.membershipType
})
case actionTypes.MOVE_FORWARDS:
const currentId = action.currentSection.id
const currentName = "section" + action.currentSection.currentName
return Object.assign({}, state, {
currentName: {
id: currentId,
collapse: true
}
})
default: return state
}
}
The currentName variable is causing an issue for the state to not update. I want to be able to dynamically change each sections state, which is why I thought I'd be able have a variable and update state like this.
It seems you can't use a variable for the key in the key/value pair. Why is this? What's an alternative to dynamically updating state?
That is because JavaScript understands that you want to create a key named currentName not a key with the value of the variable currentName. In order to do what you want, you have to wrap currentName in brackets:
return Object.assign({}, state, {
[currentName]: {
id: currentId,
collapse: true
}
})
So it will understand that the key will be whatever currentName is.
It also right:
return Object.assign({}, state, {
[currentName]: Object.assign({}, state[currentName], {
id: currentId,
collapse: true
})
})

Resources