I am trying to add redux to Next.js. It is not working Ok
This is my reducerSlice file
import { createSlice } from '#reduxjs/toolkit';
import { HYDRATE } from 'next-redux-wrapper';
import { AppState } from '..';
export const ProfileSlice = createSlice({
name: 'profile',
initialState: {
name: [] as any
},
reducers: {
setProfileData: (state, action) => {
state.name = [...state.name, action.payload];
}
},
extraReducers: {
[HYDRATE]: (state, action) => {
if (!action.payload.profile.name) {
return state;
}
const nextState = {
...state, // use previous state
name: [...state.name, ...action.payload.profile.name]
};
return nextState;
}
}
});
export const { setProfileData } = ProfileSlice.actions;
export const selectProfile = (state:AppState)=>state.profile;
export default ProfileSlice.reducer;
When I use server side rendering to dispatch data to the store a single value is stored okay but when i use array the hydration is called multiple times
You can see in this image Emma is logged 4 times
I don't know what to do
Here is how I used SSR in profile file
export const getServerSideProps = wrapper.getServerSideProps(store => async ({ query }) => {
console.log('store state on the server before dispatch', store.getState());
const response = await fetch(
`https://reqres.in/api/users/${Math.floor(Math.random() * 10 + 1)}`
);
const { data } = await response.json();
const data2 = data.first_name;
store.dispatch(setProfileData(`${data.first_name}`));
return {
props: {
data2
}
};
});
Related
I am trying to auth session by random user with http get request and createAsyncThunk.
fetching the user data in App.js on mount hook.
I can see the get request in my network and the new fetched state in redux dev tool,
but my TopBar.js useSelector return the initial state before the fetch.
TopBar.js user log:
App.js:
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchRandomUserData())
}, [dispatch]);
authSlice.js:
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
const initialState = {
isLoggedIn: true,
user: {},
loading: false,
error: null,
};
export const fetchRandomUserData = createAsyncThunk(
'auth/fetchRandomUser',
async () => {
try {
const response = await fetch('https://randomuser.me/api/');
const data = await response.json();
return data.results[0];
} catch (error) {
throw Error(error);
}
}
);
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
logout(state, action) {
state.isLoggedIn = false;
},
},
extraReducers: {
[fetchRandomUserData.pending]: (state, action) => {
state.loading = true;
state.error = null;
},
[fetchRandomUserData.fulfilled]: (state, action) => {
console.log("action.payload",action.payload);
state.user = action.payload;
state.loading = false;
},
[fetchRandomUserData.rejected]: (state, action) => {
state.error = action.error.message;
state.loading = false;
},
},
});
export const { logout } = authSlice.actions;
export default authSlice.reducer;
store.js
import { configureStore } from '#reduxjs/toolkit';
// import booksReducer from './reducers/booksReducer';
import booksReducer from './slices/bookSlice';
import authReducer from './slices/authSlice';
const store = configureStore({
reducer: { books: booksReducer, auth: authReducer },
});
export default store;
TopBat.js:
export default function TopBar(): JSX.Element {
const user = useSelector((state: any) => state.auth);
console.log("topbar",user); // returns the initial state
//....
Please make sure that you update react-redux to version 8 if you are using react 18.
There are known cases where components just stop updating if you are using react-redux 7 and lower with react 18.
I am new in redux and redux-toolkit. I am trying to get data from api.
I get error and can not receive data from api. I am using redux-toolkit library.
This is my App.js:
function App() {
const companies = useSelector(state => state.companyList);
console.log(companies)
return (
<div className="App">
<header className="App-header">
{companies.map(company => {
return(
<h1>{company.name}</h1>
)
})}
<h1>hello</h1>
</header>
</div>
);
}
export default App;
This is createSlice.js
const getCompanies = axios.get(
"https://mocki.io/v1/d4867d8b-b5d5-4a48-a4ab-79131b5809b8"
).then((response) => {
console.log(response.data)
return response.data;
}).catch((ex) => {
console.log(ex)
})
export const companySlice = createSlice({
name: "companyList",
initialState: {value: getCompanies},
reducers: {
addCompnay: (state, action) => {},
},
});
export default companySlice.reducer;
Here is store.js
import { configureStore } from "#reduxjs/toolkit";
import companyReducer from "./features/companySlice/compnayList";
export const store = configureStore({
reducer:{
companyList: companyReducer,
}
})
In the browser, I receive this error:
enter image description here
You are making a lot of mistakes here so be sure to check out some tutorials and docs: https://redux-toolkit.js.org/tutorials/quick-start
You need to use createAsyncThunk and handle the response in extraReducers: https://redux-toolkit.js.org/rtk-query/usage/migrating-to-rtk-query#implementation-using-createslice--createasyncthunk
In companySlice:
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
export const getCompanies = createAsyncThunk(
"companyList/getCompanies",
async () => {
try {
const response = await axios.get(
"https://mocki.io/v1/d4867d8b-b5d5-4a48-a4ab-79131b5809b8"
);
return response.data;
} catch (error) {
console.error(error);
}
});
const companySlice = createSlice({
name: "companyList",
initialState: {
company: {},
isLoading: false,
hasError: false
},
extraReducers: (builder) => {
builder
.addCase(getCompanies.pending, (state, action) => {
state.isLoading = true;
state.hasError = false;
})
.addCase(getCompanies.fulfilled, (state, action) => {
state.company = action.payload;
state.isLoading = false;
state.hasError = false
})
.addCase(getCompanies.rejected, (state, action) => {
state.hasError = true
state.isLoading = false;
})
}
});
// Selectors
export const selectCompanies = state => state.companyList.company;
export const selectLoadingState = state => state.companyList.isLoading;
export const selectErrorState = state => state.companyList.hasError;
export default companySlice.reducer;
Then you import selectCompanies wherever you want to use it and access it with useSelector.
In App.js:
import { useSelector } from "react-redux";
import { selectCompanies } from "WHEREEVER selectCompanies IS EXPORTED FROM";
function App() {
// Company list state
const companies = useSelector(selectCompanies);
// Rest of the code
.....
.....
.....
.....
}
export default App;
I am trying to implement Redux Toolkit and Redux Saga with Firebase. Currently I am stuck with the problem of fetching the data from the Firestore. I have configured the store and everything that is needed for the Redux Saga and Toolkit, now the only problem I have is when I try to fetch the data from the Firestore. I have made also the helper functions which can and already did help me, but just without the Redux involved.
This is what I mean by this:
store.js
import createSagaMiddleware from "redux-saga";
import { configureStore } from "#reduxjs/toolkit";
import allQuestionsReducer from "./allQuestionsState";
import allQuestionsSaga from "./redux-saga/allQuestionsSaga";
const saga = createSagaMiddleware();
const store = configureStore({
reducer: {
allQuestions: allQuestionsReducer,
},
middleware: [saga],
});
saga.run(allQuestionsSaga);
export { store };
AllQuestionsState.js
import { createSlice } from "#reduxjs/toolkit";
export const allQuestionSlice = createSlice({
name: "allQuestions",
initialState: {
allQuestions: [],
isLoading: false,
error: null,
},
reducers: {
getAllQuestionsFetch: (state, action) => {
state.isLoading = true;
},
getAllQuestionsSuccess: (state, action) => {
state.allQuestions = action.payload;
state.isLoading = false;
},
getAllQuestionsError: (state, action) => {
state.error = action.payload;
state.isLoading = false;
},
},
});
export const {
getAllQuestionsFetch,
getAllQuestionsSuccess,
getAllQuestionsError,
} = allQuestionSlice.actions;
export default allQuestionSlice.reducer;
AllQuestionsSaga.js
import { call, put, takeEvery } from "redux-saga/effects";
import { getQuestions } from "../../utils/firebase-functions/firebase-functions";
import { getAllQuestionsSuccess } from "../allQuestionsState";
function* workGetAllQuestions() {
const response = yield new Promise(getQuestions);
yield put(getAllQuestionsSuccess(response));
}
function* allQuestionsSaga() {
yield takeEvery("allQuestions/getAllQuestionsFetch", workGetAllQuestions);
}
export default allQuestionsSaga;
Helper Function
export const getQuestions = async (formData) => {
let error;
try {
const success = await onSnapshot(collection(firebaseDatabase, "questions"));
return success.docs.map((doc) => ({ ...doc.data(), id: doc.id }));
} catch (error) {
error = error.message;
return error;
}
};
Where the action is dispatched
const dispatch = useDispatch();
const data = useSelector(
(state) => state.allQuestions.allQuestions
);
useEffect(() => {
dispatch(getAllQuestionsFetch());
}, []);
console.log(data);
Does anyone has any clue on how to fix this. I am getting the [] even though I have data in Firestore.
I have this reducer
import { createSlice } from "#reduxjs/toolkit";
export const projectSlice = createSlice({
name: "project-redux",
initialState: {
name: "",
},
reducers: {
get_project: (state, action) => {
axios
.get("http://localhost:5000/api/project/" + action.payload)
.then((res) => {
state = res.data; //which contains the name
});
},
},
});
export const { get_project } = projectSlice.actions;
export default projectSlice.reducer;
and want to access the "name" with useAppSelector
const dispatch = useAppDispatch();
const {name}=useAppSelector(state=>state.projs) //projs is the name of projectsReducer in the store
console.log(name) // only give initial state
How do I get the 'name' value after the get request is fulfilled?
Solution:
export const fetchProject = createAsyncThunk(
"fetchProject",
async (id) => {
const res = await axios.get("http://localhost:5000/api/project/" + id);
return res.data.title;
}
);
reducers: {//other reducers if ther is}
extraReducers: (builder) => {
builder.addCase(fetchProject.fulfilled, (state, action) => {
state.title = action.payload;
});
},
then:
const name = useAppSelector((state) => state.projs.title);
console.log("selecttitle", name);
You can't put the side effect(I/O operations) code inside reducer functions. The redux reducer function should be pure. You should use createAsyncThunk to fetch data.
After your dispatch the async thunk, you should mutate the state with the fetched data inside extraReducers field of createSlice. After mutating the state, the component will re-render, then the useAppSelector will be called. You will read the state fetched from the remote server from the redux store.
E.g.
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit';
import axios from 'axios';
import { useEffect } from 'react';
export const fetchProject = createAsyncThunk('fetchProject', (id) => {
return axios.get('http://localhost:5000/api/project/' + id).then((res) => res.data);
});
export const projectSlice = createSlice({
name: 'project-redux',
initialState: {
name: '',
},
reducers: {},
extraReducers: (builder) => {
builder.addCase(fetchProject.fulfilled, (state, action) => {
state.name = action.payload;
});
},
});
export default projectSlice.reducer;
// Component
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
function Test() {
const dispatch = useDispatch();
const { name } = useSelector((state) => state.projs);
useEffect(() => {
dispatch(fetchProject('1'));
}, [dispatch]);
return <div>{name}</div>;
}
I need to fetch data from server with the help of such instrument as Redux. I watched some tutorials about it and wrote some code for it. Here it is:
actions/fetching_actions.js
import * as Actions from '../constants/action_types';
function fetchListOfCities() {
return fetch(`${Actions.BASE_URL}/data/2.5/find?lat=55.5&lon=37.5&cnt=10&appid=8df903ce56f6d18245e72f380beb297d`);
}
export const listOfCitiesRequest = () => function (dispatch) {
return fetchListOfCities()
.then(list => list.json())
.then((list) => {
dispatch(getListOfCities(list));
}).catch((error) => {
console.log(error);
});
};
export const getListOfCities = result => ({
type: Actions.LIST_RESPONSE,
result,
});
constants/action_types.js
export const BASE_URL = 'http://api.openweathermap.org';
export const LIST_RESPONSE = 'LIST_RESPONSE';
export const CITY_RESPONSE = 'CITY_RESPONSE';
reducers/fetching_reducer.js
import * as Actions from '../constants/action_types';
const initialState = {
list: [],
city: {},
};
const FETCHING_REDUCER = (state = initialState, action) => {
switch (action.type) {
case Actions.LIST_RESPONSE:
return {
...state,
list: action.result,
};
case Actions.CITY_RESPONSE:
return {
...state,
city: action.result,
};
default:
return state;
}
};
export default FETCHING_REDUCER;
reducers/index.js
import * as Actions from '../constants/action_types';
const initialState = {
list: [],
city: {},
};
const FETCHING_REDUCER = (state = initialState, action) => {
switch (action.type) {
case Actions.LIST_RESPONSE:
return {
...state,
list: action.result,
};
case Actions.CITY_RESPONSE:
return {
...state,
city: action.result,
};
default:
return state;
}
};
export default FETCHING_REDUCER;
And unfortunately I don't know what should I do further. Before I fetched data in this way in Component:
getCitiesListFromApiAsync = async () => {
const fetchData = await fetch('http://api.openweathermap.org/data/2.5/find?lat=55.5&lon=37.5&cnt=10&appid=8df903ce56f6d18245e72f380beb297d').then();
const data = await fetchData.json();
if (data.cod !== '200') {
Alert.alert('Loading failed');
} else {
this.setState({ data });
}
};
But I heard that it's better to fetch data by redux, so, please, can you explain me how to finish this fetching part, what should I add here?
In saga please import these things
import { put } from 'redux-saga/effects';
getCitiesListFromApiAsync = async () => {
const fetchData = await fetch('http://api.openweathermap.org/data/2.5/find?lat=55.5&lon=37.5&cnt=10&appid=8df903ce56f6d18245e72f380beb297d').then();
const data = await fetchData.json();
if (data.cod !== '200') {
Alert.alert('Loading failed');
} else {
yield put({ type: LIST_RESPONSE, payload: data });
}
};
In reducer
switch (action.type) {
case Actions.LIST_RESPONSE:
return {
...state,
list: action.payload,
};
To send some request to the server via redux you should use one of middlewares:
redux-thunk
redux-promise
redux-saga
redux-observable
etc.
The easiest I think is redux-thunk.
1. Install the package:
$ npm install redux-thunk
2. Connect it to your store:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
// Note: this API requires redux#>=3.1.0
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
After that, you will be allowed to dispatch to redux not only a plain javascript object but also functions.
3. So you can dispatch like this:
store.dispatch(listOfCitiesRequest());