I have a list of songs, when I click to any of them I open the player modal then send a request to increment count view for this song,
And at the same time, I display in the detailed song the count view, but it renders the previous last count
For example,
This song has 30 views after I send a request it should be 31,
But it's still 30 im song details
So is there a way to make it a real-time?
Code
Just to explain
I get songs from API then pass it to manipulation function,
then save it in the state, after user click on any song in the list I dispatch an action that's to save all list in the redux store,
Reducer
let initialState = {
allSongs: [],
currentIndex: 0,
};
const songsInPlayerReducer = (state = initialState, action) => {
switch (action.type) {
case SONGS_IN_PLAYER:
return {
...state,
allSongs: action.songs,
currentIndex: action.index,
};
default:
return state;
}
};
export default songsInPlayerReducer;
Action
export const saveSongsPlayer = (songs, index) => {
return {
type: SONGS_IN_PLAYER,
songs,
index,
};
};
Home screen ( get songs from API )
manipulateArray = async array => {
let songs = [];
array.map(track =>
songs.push({
id: track.id,
name: track.name,
countview: track.countview,
url: this.state.url + track.sounds,
img: this.state.url + track.avatar,
}),
);
return songs;
};
getRecentSongs = async () => {
try {
let response = await API.get('/index');
let {recent_tracks} = response.data.data;
let recent_songs = await this.manipulateArray(recent_tracks);
this.setState({
recent_songs,
loading: true,
});
} catch (error) {
console.log(error);
this.setState({error: true});
}
};
// Here a function I call after I press to any song in List
playSong = index => {
this.props.saveSongs(this.state.recent_songs, index);
};
Music player component // I just render the count views (from redux store) and send a request to API to increment the song views
incremntSongView = async () => {
const {songs, currentIndex} = this.props;
let id = songs[currentIndex].id;
try {
await API.post('/increnentView', {
id,
});
console.log('increment Done');
} catch (err) {
console.log(err);
}
};
onLoad = data => {
this.setState(
{
duration: Math.floor(data.duration),
loading: false,
},
() => this.incremntSongView(),
);
}
<Text>
{this.props.songs[this.props.currentInx].countview}
</Text>
Related
I am having a problem with saving a value from the client to backend server, what it's suppose to do is on button click essentially save the previous value and increase it by one
VoteReducer
import {
FETCH_SERVERID,
FETCH_VOTE,
UPDATE_VOTE,
INCREASE_VOTE
} from './constants';
const initialState = {
serverID: '',
voteCount: '',
};
const voteReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_SERVERID:
return {
...state,
serverID: action.payload
};
case FETCH_VOTE:
return {
...state,
fetchVote: action.payload
};
case UPDATE_VOTE:
return {
...state,
voteCount: {
...state.voteCount, ...action.payload,
}
}
case INCREASE_VOTE:
return state + 1
default:
return state;
};
};
export default voteReducer;
Vote Actions
import { push } from 'connected-react-router';
import axios from 'axios';
import { success } from 'react-notification-system-redux';
import {
FETCH_SERVERID,
FETCH_VOTE,
UPDATE_VOTE,
INCREASE_VOTE
} from './constants';
import handleError from "../../utils/error";
export const fetchServerID = value => {
return {
type: FETCH_SERVERID,
payload: value
};
};
export const fetchVote = value => {
return {
type: FETCH_VOTE,
payload: value
};
};
export const increaseVote = () => {
return {
type: INCREASE_VOTE,
};
};
export const voteCount = vote => {
return async (dispatch, getState) => {
try {
const voteValues = {
serverID: "",
voteCount: + 1
}
const voteCount = localStorage.getItem('vote_button')
const newVote = {
votedServer: voteValues.serverID,
voteCount: voteValues.voteCount,
};
const successOptions = {
title: "Your vote has been succesfully recorded",
position: 'tr',
autodismiss: 1
};
const increasingVote = {
title: "We are increasing your vote count test ",
position: 'tr',
autodismis: 1
};
dispatch(success(increaseVote));
const response = await axios.post('/api/vote/add', newVote);
if(response.data.success === true) {
dispatch(success(successOptions));
} else {
const retryOptions = {
title: "Please properly vote on a server",
position: 'tr',
autodismiss: 1
};
dispatch(warning(retryOptions));
}
} catch (error) {
handleError(error, dispatch);
}
}
}
Server side code
const express = require('express');
const router = express.Router();
// Models/helpers
const Vote = require('../../models/vote');
router.post('/add', async (req, res) => {
const serverID = req.body.serverID;
const voteCount = req.body.voteCount;
const vote = new Vote({
serverID,
voteCount,
});
const voteSave = await vote.save();
await vote.save(async (err, data) => {
if (err) {
return res.status(400).json({
error: 'Your request could not be completed at this time'
});
}
res.status(200).json({
success: true,
message: 'We are now counting your vote!',
vote: voteSave
});
});
});
module.exports = router;
Vote Index
class Vote extends React.PureComponent {
render() {
const {
user,
vote,
voteCount,
} = this.props;
return (
<div className='vote-page'>
<button
variant='primary'
text='Vote'
className='vote_button'
onClick={() => { voteCount(vote) }}>
>Vote</button>
</div>
);
}
}
const mapStateToProps = state => {
return {
user: state.account.user,
voteCount: state.voteCount,
};
};
export default connect(mapStateToProps, actions)(Vote);
I literally started learning full stack development this week, uhm just trying to grasp advanced concepts but essentially what I want it to do is keep track of votes then increase it by one when a button is pressed/vote on from the client then passed to the server saved but increased by one every-time a user presses a vote on a specific page but I haven't got to the point of getting that to work nor have I gotten to the point of implementing a way for each type of listing to be different from one another when voted on
Hopefully someone answers soon! Thanks
I use next-redux-wrapper, MSW, #mswjs/data and redux-toolkit for storing my data in a store as well as mocking API calls and fetching from a mock Database.
I have the following scenario happening to me.
I am on page /content/editor and in the console and terminal, I can see the data was fetched from the mock database and hydrated from getStaticProps of Editor.js. So now IDs 1 to 6 are inside the store accessible.
Now I click on the PLUS icon to create a new project. I fill out the dialog and press "SAVE". a POST request starts, it's pending and then it gets fulfilled. The new project is now in the mock DB as well as in the store, I can see IDs 1 to 7 now.
Since I clicked "SAVE" and the POST request was successful, I am being routed to /content/editor/7 to view the newly created project.
Now I am on Page [id].js, which also fetched data from the mock DB and then it gets stored and hydrated into the redux store. The idea is, it takes the previous store's state and spreads it into the store, with the new data (if there are any).
Now the ID 7 no longer exists. And IDs 1 to 6 also don't exist anymore, instead, I can see in the console and terminal that IDs 8 to 13 were created, and the previous ones are no more.
Obviously, this is not great. When I create a new project and then switch the route, I should be able to access the newly created project as well as the previously created ones. But instead, they all get overwritten.
It either has something to do with the next-redux-wrapper or MSW, but I am not sure how to make it work. I need help with it. I will post some code now:
Code
getStaticProps
// path example: /content/editor
// Editor.js
export const getStaticProps = wrapper.getStaticProps(
(store) =>
async ({ locale }) => {
const [translation] = await Promise.all([
serverSideTranslations(locale, ['editor', 'common', 'thesis']),
store.dispatch(fetchProjects()),
store.dispatch(fetchBuildingBlocks()),
]);
return {
props: {
...translation,
},
};
}
);
// path example: /content/editor/2
// [id].js
export const getStaticProps = wrapper.getStaticProps(
(store) =>
async ({ locale, params }) => {
const { id } = params;
const [translation] = await Promise.all([
serverSideTranslations(locale, ['editor', 'common', 'thesis']),
store.dispatch(fetchProjects()),
// store.dispatch(fetchProjectById(id)), // issue: fetching by ID returns null
store.dispatch(fetchBuildingBlocks()),
]);
return {
props: {
...translation,
id,
},
};
}
);
Mock Database
Factory
I am going to shorten the code to the relevant bits. I will remove properties for a project, as well es helper functions to generate data.
const asscendingId = (() => {
let id = 1;
return () => id++;
})();
const isDevelopment =
process.env.NODE_ENV === 'development' || process.env.STORYBOOK || false;
export const projectFactory = () => {
return {
id: primaryKey(isDevelopment ? asscendingId : nanoid),
name: String,
// ... other properties
}
};
export const createProject = (data) => {
return {
name: data.name,
createdAt: getUnixTime(new Date()),
...data,
};
};
/**
* Create initial set of tasks
*/
export function generateMockProjects(amount) {
const projects = [];
for (let i = amount; i >= 0; i--) {
const project = createProject({
name: faker.lorem.sentence(faker.datatype.number({ min: 1, max: 5 })),
dueDate: date(),
fontFamily: getRandomFontFamily(),
pageMargins: getRandomPageMargins(),
textAlign: getRandomTextAlign(),
pageNumberPosition: getRandomPageNumberPosition(),
...createWordsCounter(),
});
projects.push(project);
}
return projects;
}
API Handler
I will shorten this one to GET and POST requests only.
import { db } from '../../db';
export const projectsHandlers = (delay = 0) => {
return [
rest.get('https://my.backend/mock/projects', getAllProjects(delay)),
rest.get('https://my.backend/mock/projects/:id', getProjectById(delay)),
rest.get('https://my.backend/mock/projectsNames', getProjectsNames(delay)),
rest.get(
'https://my.backend/mock/projects/name/:id',
getProjectsNamesById(delay)
),
rest.post('https://my.backend/mock/projects', postProject(delay)),
rest.patch(
'https://my.backend/mock/projects/:id',
updateProjectById(delay)
),
];
};
function getAllProjects(delay) {
return (request, response, context) => {
const projects = db.project.getAll();
return response(context.delay(delay), context.json(projects));
};
}
function postProject(delay) {
return (request, response, context) => {
const { body } = request;
if (body.content === 'error') {
return response(
context.delay(delay),
context.status(500),
context.json('Server error saving this project')
);
}
const now = getUnixTime(new Date());
const project = db.project.create({
...body,
createdAt: now,
maxWords: 10_000,
minWords: 7000,
targetWords: 8500,
potentialWords: 1500,
currentWords: 0,
});
return response(context.delay(delay), context.json(project));
};
}
// all handlers
import { buildingBlocksHandlers } from './api/buildingblocks';
import { checklistHandlers } from './api/checklist';
import { paragraphsHandlers } from './api/paragraphs';
import { projectsHandlers } from './api/projects';
import { tasksHandlers } from './api/tasks';
const ARTIFICIAL_DELAY_MS = 2000;
export const handlers = [
...tasksHandlers(ARTIFICIAL_DELAY_MS),
...checklistHandlers(ARTIFICIAL_DELAY_MS),
...projectsHandlers(ARTIFICIAL_DELAY_MS),
...buildingBlocksHandlers(ARTIFICIAL_DELAY_MS),
...paragraphsHandlers(ARTIFICIAL_DELAY_MS),
];
// database
import { factory } from '#mswjs/data';
import {
buildingBlockFactory,
generateMockBuildingBlocks,
} from './factory/buildingblocks.factory';
import {
checklistFactory,
generateMockChecklist,
} from './factory/checklist.factory';
import { paragraphFactory } from './factory/paragraph.factory';
import {
projectFactory,
generateMockProjects,
} from './factory/project.factory';
import { taskFactory, generateMockTasks } from './factory/task.factory';
export const db = factory({
task: taskFactory(),
checklist: checklistFactory(),
project: projectFactory(),
buildingBlock: buildingBlockFactory(),
paragraph: paragraphFactory(),
});
generateMockProjects(5).map((project) => db.project.create(project));
const projectIds = db.project.getAll().map((project) => project.id);
generateMockTasks(20, projectIds).map((task) => db.task.create(task));
generateMockBuildingBlocks(10, projectIds).map((block) =>
db.buildingBlock.create(block)
);
const taskIds = db.task.getAll().map((task) => task.id);
generateMockChecklist(20, taskIds).map((item) => db.checklist.create(item));
Project Slice
I will shorten this one as well to the relevant snippets.
// projects.slice.js
import {
createAsyncThunk,
createEntityAdapter,
createSelector,
createSlice,
current,
} from '#reduxjs/toolkit';
import { client } from 'mocks/client';
import { HYDRATE } from 'next-redux-wrapper';
const projectsAdapter = createEntityAdapter();
const initialState = projectsAdapter.getInitialState({
status: 'idle',
filter: { type: null, value: null },
statuses: {},
});
export const fetchProjects = createAsyncThunk(
'projects/fetchProjects',
async () => {
const response = await client.get('https://my.backend/mock/projects');
return response.data;
}
);
export const saveNewProject = createAsyncThunk(
'projects/saveNewProject',
async (data) => {
const response = await client.post('https://my.backend/mock/projects', {
...data,
});
return response.data;
}
);
export const projectSlice = createSlice({
name: 'projects',
initialState,
reducers: {
// irrelevant reducers....
},
extraReducers: (builder) => {
builder
.addCase(HYDRATE, (state, action) => {
// eslint-disable-next-line no-console
console.log('HYDRATE', action.payload);
const statuses = Object.fromEntries(
action.payload.projects.ids.map((id) => [id, 'idle'])
);
return {
...state,
...action.payload.projects,
statuses,
};
})
.addCase(fetchProjects.pending, (state, action) => {
state.status = 'loading';
})
.addCase(fetchProjects.fulfilled, (state, action) => {
projectsAdapter.addMany(state, action.payload);
state.status = 'idle';
action.payload.forEach((item) => {
state.statuses[item.id] = 'idle';
});
})
.addCase(saveNewProject.pending, (state, action) => {
console.log('SAVE NEW PROJECT PENDING', action);
})
.addCase(saveNewProject.fulfilled, (state, action) => {
projectsAdapter.addOne(state, action.payload);
console.group('SAVE NEW PROJECT FULFILLED');
console.log(current(state));
console.log(action);
console.groupEnd();
state.statuses[action.payload.id] = 'idle';
})
// other irrelevant reducers...
},
});
This should be all the relevant code. If you have questions, please ask them and I will try to answer them.
I have changed how the state gets hydrated, so I turned this code:
.addCase(HYDRATE, (state, action) => {
// eslint-disable-next-line no-console
console.log('HYDRATE', action.payload);
const statuses = Object.fromEntries(
action.payload.projects.ids.map((id) => [id, 'idle'])
);
return {
...state,
...action.payload.projects,
statuses,
};
})
Into this code:
.addCase(HYDRATE, (state, action) => {
// eslint-disable-next-line no-console
console.group('HYDRATE', action.payload);
const statuses = Object.fromEntries(
action.payload.projects.ids.map((id) => [id, 'idle'])
);
state.statuses = { ...state.statuses, ...statuses };
projectsAdapter.upsertMany(state, action.payload.projects.entities);
})
I used the adapter to upsert all entries.
I have this app which uses the first createAsyncThunk to get the first page from the API, then I want the second createAsyncThunk, which gets the next page, to fire when the user reaches the bottom of the page and get the data in the infinite scrolling method.
// Gets the First 10 Posts from the API
export const getPosts = createAsyncThunk(
"post/getPosts",
async (apiAddress) => {
const response = await fetch(apiAddress);
if (!response.ok) throw new Error("Request Failed!");
const data = await response.json();
return data;
}
);
// Loads the Next 10 Posts
export const getMorePosts = createAsyncThunk(
"post/getMorePosts",
async (apiAddress) => {
const response = await fetch(apiAddress);
if (!response.ok) throw new Error("Request Failed!");
const data = await response.json();
return data;
}
);
const redditPostSlice = createSlice({
name: "post",
initialState: {
redditPost: {},
isLoading: false,
hasError: false,
moreIsLoading: false,
moreHasError: false,
},
extraReducers: (builder) => {
builder
.addCase(getPosts.pending, (state) => {
state.isLoading = true;
state.hasError = false;
})
.addCase(getPosts.fulfilled, (state, action) => {
state.redditPost = action.payload.data;
state.isLoading = false;
state.hasError = false;
})
.addCase(getPosts.rejected, (state) => {
state.isLoading = false;
state.hasError = true;
})
.addCase(getMorePosts.pending, (state) => {
state.moreIsLoading = true;
state.moreHasError = false;
})
.addCase(getMorePosts.fulfilled, (state, action) => {
state.redditPost = action.payload.data;
state.moreIsLoading = false;
state.moreHasError = false;
})
.addCase(getMorePosts.rejected, (state) => {
state.moreIsLoading = false;
state.moreHasError = true;
});
},
});
My problem is that the state of the app changes to the second page and the first page contents are gone.
I know my problem is here state.redditPost = action.payload.data but I don't know how I can append this new state to the previous one.
I've been at this for hours and don't really know what to do anymore.
Is there any way to append the new state to the previous state?
I would assume that the payload data has an array of children. Like this example of response found online:
{
kind: "Listing",
data: {
...
children: [
{kind: "t3", data: {...}}
{kind: "t3", data: {...}}
{kind: "t3", data: {...}}
...
]
...
}
}
So you would need to make redditPost be an array. Also semantically is should be redditPosts to denote array.
initialState: {
redditPost: {},
...
and then when you're updating it one of the easiest ways is using ES6 spread
state.redditPost = {
...state.redditPost,
after: action.payload.data.after,
children: [
...state.redditPost.children,
...action.payload.data.children
]
}
I'm trying to load the second set of items from the reddit API with Infinite scrolling when the user scrolls to the bottom of the page and although they do load successfully, the previous items are overridden by the new ones.
You can see this happening here: https://reddix.netlify.app/
This is the Redux Slice with the Thunks:
// Gets the first 10 Posts from the API
export const getPosts = createAsyncThunk(
"post/getPosts",
async (apiAddress) => {
const response = await fetch(apiAddress);
if (!response.ok) throw new Error("Request Failed!");
const data = await response.json();
return data;
}
);
// Loads the Next 10 Posts
export const getMorePosts = createAsyncThunk(
"post/getMorePosts",
async (apiAddress) => {
const response = await fetch(apiAddress);
if (!response.ok) throw new Error("Request Failed!");
const data = await response.json();
return data;
}
);
const redditPostSlice = createSlice({
name: "post",
initialState: {
redditPost: {},
isLoading: false,
hasError: false,
moreIsLoading: false,
moreHasError: false,
},
extraReducers: (builder) => {
builder
.addCase(getPosts.pending, (state) => {
state.isLoading = true;
state.hasError = false;
})
.addCase(getPosts.fulfilled, (state, action) => {
state.redditPost = action.payload.data;
state.isLoading = false;
state.hasError = false;
})
.addCase(getPosts.rejected, (state) => {
state.isLoading = false;
state.hasError = true;
})
.addCase(getMorePosts.pending, (state) => {
state.moreIsLoading = true;
state.moreHasError = false;
})
.addCase(getMorePosts.fulfilled, (state, action) => {
state.redditPost = action.payload.data;
state.moreIsLoading = false;
state.moreHasError = false;
})
.addCase(getMorePosts.rejected, (state) => {
state.moreIsLoading = false;
state.moreHasError = true;
});
},
});
And in this Search components I have the functionality for loading the pages:
const Search = () => {
const [input, setInput] = useState("");
const [isFetching, setIsFetching] = useState(false);
const redditPost = useSelector(selectRedditPost);
const dispatch = useDispatch();
// Get the Last Post
const lastPost = () => {
if (redditPost.children) {
const [lastItem] = redditPost.children.slice(-1);
const lastKind = lastItem.kind;
const lastId = lastItem.data.id;
return `${lastKind}_${lastId}`;
} else {
return;
}
};
// API Endpoints
const hotApiAddress = `https://www.reddit.com/r/${input}/hot.json?limit=10`;
const newApiAddress = `https://www.reddit.com/r/${input}/new.json?limit=10`;
const moreApiAddress = `https://www.reddit.com/r/${input}/new.json?limit=10&after=${lastPost()}`;
// Get Hot Posts
const handleHot = (e) => {
e.preventDefault();
if (!input) return;
dispatch(getPosts(hotApiAddress));
};
// Get New Posts
const handleNew = (e) => {
e.preventDefault();
if (!input) return;
dispatch(getPosts(newApiAddress));
};
// Fire Upon Reaching the Bottom of the Page
const handleScroll = () => {
if (
window.innerHeight + document.documentElement.scrollTop !==
document.documentElement.offsetHeight
)
return;
setIsFetching(true);
};
// Debounce the Scroll Event Function and Cancel it When Called
const debounceHandleScroll = debounce(handleScroll, 100);
useEffect(() => {
window.addEventListener("scroll", debounceHandleScroll);
return () => window.removeEventListener("scroll", debounceHandleScroll);
}, [debounceHandleScroll]);
debounceHandleScroll.cancel();
// Get More Posts
const loadMoreItems = useCallback(() => {
dispatch(getMorePosts(moreApiAddress));
setIsFetching(false);
}, [dispatch, moreApiAddress]);
useEffect(() => {
if (!isFetching) return;
loadMoreItems();
}, [isFetching, loadMoreItems]);
Is there any way to keep the previous items when the next set loads?
Because you set on every dispatch a different payload value, your previous array disappears. Take a look at the entityAdapter. Whit this adapter you can easily manage arrays, you can add, modify, update or remove items from the array. This is can be a solution for you.
Keep in a list the previous value, and when a next action is dispatched, append the existing list.
Note: you need the upsertMany method on the entityAdapter to keep the previous values.
Other solutions without entityAdapter:
You have to store the array in the state somehow because when another payload appears you have to access this array for example state.redditPosts = [...state.redditPosts, ...payload.array]. Or because you use redux js toolkit, you can mutate the state, state.redditPosts.push(...payload.array)
I have a NotifiCard component and a ReplyComment component
When I run the LookforReply function, the GETONECOMMENT_REQUEST and LOAD_POST_REQUEST dispatches are executed, and the data comes into onecomment.
which is
const {onecomment} = useSelector((state) => state.post);
And I also want to pass the {item:one comment} data in the ReplyComment using navigation.navigate.
However, when we run our code, the data does not come into ReplyComment immediately, so it causing an error.
this is my code
(NotifiCard.js)
const NotifiCard = ({item}) => {
const dispatch = useDispatch();
const navigation = useNavigation();
const {onecomment} = useSelector((state) => state.post);
const LookforReply = useCallback(() => {
dispatch({
type:GETONECOMMENT_REQUEST,
data:item?.CommentId,
}),
dispatch({
type: LOAD_POST_REQUEST,
data:item.PostId,
}),
navigation.navigate('ReplyComment',{itemm:onecomment})
},[]);
return (
<LookContainer onPress={LookforReply}>
<Label>대댓보기</Label>
</LookContainer>
);
};
when dispatch GETONECOMMENT_REQUEST, getonecommentAPI this api run and get data from backend router
(postsaga.js)
function getonecommentAPI(data) {
return axios.get(`/post/${data}/getonecomment`);
}
function* getonecomment(action) {
try {
const result = yield call(getonecommentAPI, action.data);
yield put({
type: GETONECOMMENT_SUCCESS,
data: result.data,
});
} catch (err) {
console.error(err);
yield put({
type: GETONECOMMENT_FAILURE,
error: err.response.data,
});
}
}
which is this backend router
(backend/post.js)
router.get('/:onecommentId/getonecomment', async (req, res, next) => {
try {
// console.log("req.params.onecomment:",req.params.onecommentId);
const onecomment = await Comment.findOne({
where:{id: req.params.onecommentId},
include: [{
model: User,
attributes: ['id', 'nickname'],
}],
})
// console.log("onecomment:",JSON.stringify(onecomment));
res.status(200).json(onecomment);
} catch (error) {
console.error(error);
next(error);
}
});
if i get result data this will put draft.onecomment
(reducer/post.js)
case GETONECOMMENT_REQUEST:
draft.loadPostLoading = true;
draft.loadPostDone = false;
draft.loadPostError = null;
break;
case GETONECOMMENT_SUCCESS:
// console.log("action.data:::",action.data);
draft.loadPostLoading = false;
draft.onecomment = action.data;
draft.loadPostDone = true;
break;
case GETONECOMMENT_FAILURE:
draft.loadPostLoading = false;
draft.loadPostError = action.error;
break;
and i can get data in onecomment by using useselector at (NotifiCard.js)
const {onecomment} = useSelector((state) => state.post);
what i want is that when i press LookforReply i want to pass itemm data to
ReplyComment component but if i press LookforReply, i can't get itemm data
Immediately
(ReplyComment.js)
const ReplyComment = ({route}) => {
const {itemm} = route.params;
console.log("itemm",itemm);
return (
<Container>
<TodoListView parenteitem={itemm} />
<AddTodo item={itemm} />
</Container>
);
};
I think that the LookforReply function is executed asynchronously and navigate is executed before the onecomment data comes in, and it doesn't seem to be able to deliver the itemm.
so how can i fix my code?....