Designing react- reducer of a social site - reactjs

I have three different components in my app.
ie. 1. Newsfeed (here i am fetching all the posts)
2. Trending (posts with of popular hashtags)
3. UserProfile (fetching all the posts of that user)
const postReducer = (state = postReducerDefaultState, action) => {
switch(action.type){
case 'ADD_POST':{
return[
action.post,
...state
]
}
case 'FIND_POST':
return action.post
case 'REMOVE_POST':
return state.filter(post => post._id !== action.id)
case 'EDIT_POST':
return state.map(post => {
if (post._id === action.id){
return{
...post,
...action.updates
};
}else{
return post;
}
});
case 'LIKE_POST':
return state.map(post => {
if(post._id === action.id){
return{
...post,
likes:[...post.likes,action.likedBy]
}
}else{
return post;
}
});
case 'UNLIKE_POST':
return state.map(post => {
if(post._id === action.id){
return {
...post,
likes : post.likes.filter(liker => liker !==
action.likedBy)
}
}else{
return post;
}
})
case 'ADD_COMMENT':
return state.map(post => {
if(post._id === action.pid){
return{
...post,
comments:[...post.comments,action.comment]
}
}else{
return post;
}
});
case 'SET_POST':
return action.posts;
case 'HASHTAG_POST':
return action.posts;
default:
return state;
}
};
in this design if i go from one component to other component, (dashboard to trending), I lost all the data of the dashboard. But if want to keep the like,comment functionalty for dashboard and trending posts, then i have to write the like-comment functionality separately for all the mentioned three components. This approach will work, but i am not satisfied with as i have to write same thing thrice. Can anyone suggest the best approach?

The recommended way to solve this problem is with higher order components, which are React components that "wrap" another component to provide common functionality. You should be able to find plenty of resources explaining this, but a typical higher order component looks something like this:
import React from 'react';
const higherOrderComponent = (WrappedComponent) =>
{
class HOC extends React.Component {
render() {
return <WrappedComponent />;
}
}
return HOC;
};
You can then add whatever functionality needs to be shared to the higher order component. Then, you can use it something like this:
const SimpleHOC = higherOrderComponent(MyComponent);
EDIT: If you want the same functionality to apply for different reducer actions, that's straightforward. Just have the same callback fall through for different cases, as in this example:
case 'ACTION_1':
case 'ACTION_2':
case 'ACTION_3':
// callback here
For your application, it will be something like this:
case 'EDIT_POST':
case 'LIKE_POST':
case 'UNLIKE_POST':
return state.map(post => {
if (post._id === action.id){
return{
...post,
...action.updates
};
}else{
return post;
}
});

Related

React Reducer - Add elements to existing element

i want that new updates get continuous added to "kanbanboard" but instead the old value get signed over. enter image description here
ACTION-Function
export const editExpense = (id, updates) => ({
type: "EDIT_EXPENSE",
id,
updates
});
REDUCER-FUNCTION
case "EDIT_EXPENSE":
return state.map((expense) => {
if (expense.id === action.id) {
return {
...expense,
kanbanboard:[{...action.updates}]
};
} else {
return expense;
};
});
Thank you for helping.
I cant see the entire reducer but it looks like you are explicitly overriding the old value. Change it to:
case "EDIT_EXPENSE":
return state.map((expense) => {
if (expense.id === action.id) {
return {
...expense,
kanbanboard:[
...expense.kanbanboard, // you have to add your old state here
{
...action.updates
}]
};
} else {
return expense;
};
});

How to update the state of an object in react-redux

My question might sound stupid but I swear my brain freeze, I google it for answer but honestly I don't even know what should I search for. So basically I have a reducer, usually when I'm using redux I'm using arrays and my reducers look like this:
import { FETCH_ALL, CREATE, UPDATE, DELETE } from '../constants/actionTypes';
export default (posts = [], action) => {
switch (action.type) {
case FETCH_ALL:
return action.payload;
case CREATE:
return [...posts, action.payload];
case UPDATE:
return posts.map((post) => post._id === action.payload._id ? action.payload : post);
case DELETE:
return posts.filter((post) => post._id !== action.payload);
default:
return posts;
}
};
And now I only have an object, and I really don't know what to write on case UPDATE, I mean I'm not looping through and find my updated object and that process.
My question is how can I update if I only have an object
import { FETCH_PROFILE, UPDATE_PROFILE } from '../constants';
export default (profile= {}, action) => {
switch (action.type) {
case FETCH_PROFILE:
return action.payload;
case UPDATE_PROFILE:
return ;
default:
return profile;
}
};
like here, in that case UPDATE_PROFILE, what should I write to make it work, in my backend everything going well, the return is an object with the updated user.
I know my question is stupid, but please be kind! Thank you in advance
Unexpected token, expected "," (8:32)
6 | return action.payload;
7 | case UPDATE_PROFILE:
> 8 | return {...profile, action.payload};
| ^
9 | default:
10 | return profile;
11 | }
For object you will use spread operator just like in an array:
import { FETCH_PROFILE, UPDATE_PROFILE } from '../constants';
export default (profile= {}, action) => {
switch (action.type) {
case FETCH_PROFILE:
return action.payload;
case UPDATE_PROFILE:
return {...profile, action.payload};
default:
return profile;
}
};
More about it here.
In your update profile, pass the entire object after updating.
For eg:
let profiles =[{name:"foo",id:1},{name:"bar",id:2},{name:"baz",id:3}]
let updateProfile = [{id:2,name:"gaurav"}]
then, first update the profiles with updated profile, that is,
const updatedProfiles = profiles.map(item=>item.id===?:updatedProfile:item)
then, simply pass updatedProfiles in action.payload,
in your reducer, it will be then
case UPDATE_PROFILE:
return action.payload;

React rendering twice on click

So me and my colleague are writing a React/ServiceNow project for internal use of our company.
I'm using the reducer hook to manage state throughout the app. I've got the following structure App components, which calls the API (array with questions and their respective answers). This info is passed down to TrainingMode and that returns
return
(
<QuizContext.Provider value={{ state, dispatch }}>
<div className='container'>
<Progress />
{'Training'}
<Question />
{renderError()}
<Answers />
<button className='btn btn-primary' onClick={next}>
Confirm and Continue
</button>
</div>
</QuizContext.Provider>
);
Answers:
function Answers() {
const { state, dispatch } = useContext(QuizContext);
const { currentAnswer, currentQuestion, questions } = state;
const question = questions[currentQuestion].question;
const answers = questions[currentQuestion].answers;
console.log('curr ans is ' + question);
console.log(answers);
let firstLetter = 65;
const ans = answers.map((el) => {
return (
<Answer
key={el.sys_id}
letter={(firstLetter++).toString()}
answer={el.answer_text}
answer_sysId={el.sys_id}
selected={currentAnswer === el.sys_id}
dispatch={dispatch}
/>
);
});
return <>{ans}</>;
Individual Answer:
function Answer(props) {
let classes = ['answer'];
const handleClick = (e) => {
props.dispatch({
type: SET_CURRENT_ANSWER,
currentAnswer: props.answer_sysId
});
props.dispatch({ type: SET_ERROR, error: '' });
};
if (props.selected) {
classes.push('selected');
}
return (
<button
value={props.answer}
className={classes.join(' ')}
onClick={handleClick}
>
<span>{String.fromCharCode(props.letter)}.</span> {props.answer}
</button>
);
}
And last, the reducer:
function quizReducer(state, action) {
switch (action.type) {
case SET_CURRENT_ANSWER:
console.log(action);
state.currentAnswer.push(action.currentAnswer);
return {
...state
// currentAnswer: action.currentAnswer
};
case SET_CURRENT_QUESTION:
return {
...state,
currentQuestion: action.currentQuestion
};
case SET_ERROR:
return {
...state,
error: action.error
};
case SET_SHOW_RESULTS:
return {
...state,
showResults: action.showResults
};
case SET_ANSWERS:
return {
...state,
answers: action.answers
};
case ADD_CORRECT:
return {
...state,
correct: action.correct
};
case RESET_QUIZ:
return {
...state,
answers: [],
currentQuestion: 0,
currentAnswer: '',
showResults: false,
error: '',
correct: 0
};
default:
return state;
}
I want to click on each individual answer and push it to the currentAnswer array (so it works for both single and multiple choice questions). It kind of works, it pushes the first answer I click just once, however when I click on other answers, it pushes them twice. When I comment out the Strict mode tag, everything works fine, however I doubt that's the best solution.
console log
I've read though some articles but can't seem to fix this.
Ciao, you know that when a component is rendered, react triggers useEffect hook. So you could try to put you logs in this hook. Something like:
import { useEffect } from 'react';
...
useEffect(() => {
console.log('curr ans is ' + question);
console.log(answers);
})
If the logs appears twice, then the Answer component is rendered twice. Otherwise not.
So now a question: why you see doubled logs? My answer is I don't know. Could be related on react workflow but this is just my opinion (I'm not soo expert in react).

React Redux: Update and replace records with another records returns value of 1

React Redux: Update and replace records with another records returns value of 1.
On the server side, I have a json response [{"id":"10", "food_name":"Rice"}]
The code below works fine by displaying a food item called Rice from the database via API Call as showed in the json array above.
Now I have a requirements to replace the displayed food item Rice with Beans.
To this effect, I have a json files which is to be returned via API Call after posting
[{"id":"10", "food_name":"Beans"}]
I have also created a Post button which should send data to the server side and return the response Beans.
Here is my effort as well as my Issue which is caused by reducer.
If Implement the code below in the reducer
case foodConstants.FOOD_SUCCESS_POST:
return {
items: state.items.map(food1 => {
if (food1.id === action.id) {
//return { ...food1, food_name: state.items[0].food_name};
return { ...food1, food_name: 'Beans' };
}
The Code works fine and Rice is replaced with Beans since I set value beans in the reducer.
but since I need to get the records via API Call so if implement
case foodConstants.FOOD_SUCCESS_POST:
return {
items: state.items.map(food1 => {
if (food1.id === action.id) {
return { ...food1, food_name: state.items[0].food_name};
}
Am getting value of 1 replacing Rice instead of Beans. Please where is this value of 1 coming from.
I need to have beans replace record Rice as a value returned from API Call.
My action and service code are okay as I can see the json returned records in the array as per
[{"id":"10", "food_name":"Beans"}]
I think my problem lies in this line of code below which returns value of 1 instaed of Beans.
return { ...food1, food_name: state.items[0].food_name};
Here is the full code
import React from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { foodActions } from 'actions';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount() {
this.props.dispatch(foodActions.getFood());
}
handleFood(id,food_type) {
return (e) => this.props.dispatch(foodActions.postfood(food_id));
}
render() {
const { food1, foods1 } = this.props;
return (
<div>
{foods1.items &&
<ul>
{foods1.items.map((food1, index1) =>
<li key={food1.id}>
{food1.food_name}
<input type="button" value="Post and Update Food Name" onClick={this.handleFood(food1.id)} />
</li>
)}
</ul>
}
</div>
);
}
}
function mapStateToProps(state) {
const { foods1} = state;
const { food1 } = state;
return {
food1, foods1
};
}
const connectedApp = connect(mapStateToProps)(App);
export { connectedApp as App };
Reducer Code
import { foodConstants } from '/constants';
export function foods1(state = {}, action) {
switch (action.type) {
case foodConstants.GETALL_REQUEST:
return {loading: true};
case foodConstants.GETALL_SUCCESS:
return {
loading: false,
error: null,
items: action.foods1,
};
case foodConstants.GETALL_FAILURE:
return {
error: action.error
};
// Post and Update Food Name
case foodConstants.FOOD_REQUEST_POST:
return {...state};
case foodConstants.FOOD_SUCCESS_POST:
return {
items: state.items.map(food1 => {
if (food1.id === action.id) {
return { ...food1, food_name: state.items[0].food_name};
}
return food1;
})
};
case foodConstants.FOOD_FAILURE_POST:
return {
error: action.error
};
default:
return state
}
}
You need to replace value that is coming in action, but you are picking from state
case foodConstants.FOOD_SUCCESS_POST: {
const updatedItems = state.items.map((food1) => {
if (food1.id === action.id) {
return { ...action };
}
return food1;
});
return { ...state, items: updatedItems };
}
Or you can do this as well
case foodConstants.FOOD_SUCCESS_POST: {
let updatedItems = { ...state.items };
const itemIndex = updatedItems.findIndex((food1) => food1.id === action.id);
if(itemIndex > -1){
updatedItems[itemIndex] = {
...updatedItems[itemIndex],
...action,
}
}
return { ...state, items: updatedItems };
}

React not re-rendering when redux store changes

I know there are a lot of similar questions, but I was unable to find the right answer sifting through the others. The issue seems to be that {loopToDo} does not directly reference a prop from the store. How can I set my code up so that it updates when the store changes, like I want it to?
#connect((germzFirstStore) => {
return {
taskList: germzFirstStore.tasks
}
})
class TaskBoard extends React.Component {
render() {
function toDoStatus(value) {
return value.taskstatus === "toDo";
}
var toDoTasks = this.props.taskList.tasks.filter(toDoStatus);
var loopToDo = toDoTasks.map((tasksEntered) => {
return (
<div id={tasksEntered.idtasks} className="taskBox">{tasksEntered.task}</div>
);
});
return(
<div ref="toDo" id="toDo" className="container toDo">{loopToDo}</div>
)
}
}
the reducer:
const tasksReducer = (state=tasksInitialState, action) => {
if (action.type === "ADD") {
state = {...state, tasks: [...state.tasks, action.newTask]}
}
return state; }
The problem here is that by doing this
state = {...state, tasks: [...state.tasks, action.newTask]}
You are effectively mutating the state before returning it and that probably is the reason why your components are not re-rendering on updating state.
What you can do in your reducer is
if (action.type === "ADD") {
return {...state, tasks: [...state.tasks, action.newTask]}
}
or
if (action.type === "ADD") {
return Object.assign({}, state, {tasks: [...state.tasks, action.newTask]})
}
Hope it helps :)
If germzFirstStore.tasks has updated then the component will re-render it has nothing to do with what is inside the render function. My guess is that you are mutating the state in your reducer instead of updating it and returning the updated version of the state.

Resources