I am playing with a simple app that displays a collection of Posts from firebase and allows the user to add docs to it.
The posts are shared via a PostsContext that contains an ADD function. The useReducer gets called twice, no getting around that. The problem is that I'm writing to firebase from inside the ADD, and this results in duplicate rows.
export const PostsContext = React.createContext();
export const PostsProvider = function ({ children }) {
const reducer = function (state, action) {
switch (action.type) {
case "ADD": {
const newPost = {
id: id(),
title: action.payload.title,
content: action.payload.comment,
};
console.log("THIS GETS CALLED TWICE");
firestore.collection("posts").add(newPost);
return [newPost, ...state];
}
case "INIT": {
console.log(action.payload);
return [...action.payload.posts];
}
}
return state;
};
const [posts, dispatch] = useReducer(reducer, []);
const addPost = function (title, comment) {
dispatch({
type: "ADD",
payload: {
title,
comment,
},
});
};
const initPosts = function (posts) {
dispatch({
type: "INIT",
payload: {
posts,
},
});
};
const value = { posts, addPost, initPosts };
return (
<PostsContext.Provider value={value}>{children}</PostsContext.Provider>
);
};
I figured it out. I need to write to the DB in AddPost before calling dispatch.
That's all.
Related
I am trying to create load more functionality by fetching only the necessary date i.e. the next one that needs to be added to the existing state that I have in the redux store, but I have a problem my redux actions are duplicated.
Component App.js
function App() {
const dispatch = useDispatch();
const data = useSelector(questionsData);
useEffect(() => {
const fetchQuestions = async () => {
dispatch(fetchQuestionsBegin());
try {
const { data } = await mainUrl(`/questions?last=5`);
return dispatch(fetchQuestionsSuccess(data));
} catch (err) {
return dispatch(fetchQuestionsFailure());
}
};
fetchQuestions();
}, [dispatch]);
return (
<>
<div>TEST</div>
</>
);
}
creating store
const store = configureStore({
reducer: {
questionsStore: questionsReducer,
},
});
export default store;
slice
const initialState = {
loading: false,
questions: [],
error: "",
};
const questionsSlice = createSlice({
name: "questions",
initialState,
reducers: {
fetchQuestionsBegin(state) {
return { ...state, loading: true, error: "" };
},
fetchQuestionsSuccess(state, action) {
return {
...state,
loading: false,
questions: [...state.questions, ...action.payload],
};
},
fetchQuestionsFailure(state, action) {
return { ...state, loading: false, error: action.payload };
},
},
});
export const { reducer: questionsReducer, actions } = questionsSlice;
export const {
fetchQuestionsBegin,
fetchQuestionsSuccess,
fetchQuestionsFailure,
} = actions;
redux
When I exclude <React.StrictMode> everything works fine.
Refer to link. Strict mode can cause multiple methods to invoke multiple times. Its most likely that your redux is ran twice when the component mounts for the first time. You can implement useRef to detect initial mount and then conditionally render after
const isMounted = useRef(false)
useEffect(() => {
isMounted.current = true;
}, [])
useEffect(() => {
if (isMounted.current) {
const fetchQuestions = async () => {
dispatch(fetchQuestionsBegin());
try {
const { data } = await mainUrl(`/questions?last=5`);
return dispatch(fetchQuestionsSuccess(data));
} catch (err) {
return dispatch(fetchQuestionsFailure());
}
};
fetchQuestions();
}
}, [dispatch]);
I am trying to learn Redux. what I want to do: update the store with the data I send to the reducer. what I've done so far:
let reduxData=[1,2,3];
const reduxState = useSelector((state)=>state)
const dispatch = useDispatch();
const {setReduxData} = bindActionCreators(actionCreators, dispatch);
my action:
export const setReduxData = (data) => {
return {
type: 'setReduxData',
payload: data
}
}
my reducer:
export function reducer(state = {data: []}, action) {
switch (action.type) {
case 'setReduxData':
return {
...state,
data: [action.payload]
}
default:
return state
}
}
I am still new to both programming in general and redux so I apologize for any amateur mistakes I surely have made
let reduxData=[1,2,3];
const reduxState = useSelector((state)=>state)
const dispatch = useDispatch();
dispatch(setReduxData(reduxData)) // use it as you want e.g., on button click or any other action or in useEffect
Update your action code with the following code:
export const setReduxData = (data) =>(dispatch) {
dispatch({
type: 'setReduxData',
payload: data
});
}
trying to save in local storage bookmarked recipes but i keep getting state.bookmarkedRecipes is not iterable what I am doing wrong? Please help me!!
redux store
const reducer = combineReducers({
recipeList: recipeListReducer,
recipeDetail: recipeDetailsReducer,
bookmark: bookmarkReducer,
});
const bookmarkedRecipesFromStorage = localStorage.getItem("bookmarkedRecipes")
? JSON.parse(localStorage.getItem("bookmarkedRecipes"))
: [];
const initialState = {
bookmark:{
bookmarkedRecipes: bookmarkedRecipesFromStorage,
};
}
const middleware = [thunk];
const store = createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
bookmarkReducer
The payload which is the new bookmarked recipe will be added to the bookmarkedRecipes that was saved in the localStorage.
export const bookmarkReducer = (state = { bookmarkedRecipes: [] }, action) => {
switch (action.type) {
case BOOKMARK_ADD_RECIPE: {
return {
...state,
bookmarkedRecipes: [...state.bookmarkedRecipes, action.payload],
};
}
default:
return { state };
}
};
bookmarkAction
This is what the action looks like with the passed id it will get the recipe from the api and pass it as a payload with only the properties below.
export const addBookmark = (id) => async (dispatch, getState) => {
const { data } = await axios.get(
`https://forkify-api.herokuapp.com/api/get?rId=${id}`
);
console.log(data.recipe);
dispatch({
type: BOOKMARK_ADD_RECIPE,
payload: {
recipe: data.recipe.recipe_id,
title: data.recipe.title,
},
});
localStorage.setItem(
"bookmarkedRecipes",JSON.stringify(getState().bookmark.bookmarkedRecipes)
);
};
I am using Redux for state management and saga as a middleware. For some reason my app is in some infinite loop state of calling API endpoint.
This is my actions:
export const GET_USERS = "GET_USERS";
export const getUsers = () => ({
type: GET_USERS,
});
export const GET_USERS_SUCCESS = `${GET_USERS}_SUCCESS`;
export const getUsersSuccess = (data) => ({
type: GET_USERS_SUCCESS,
payload: data,
});
export const GET_USERS_FAIL = `${GET_USERS}_FAIL`;
export const getUsersFail = (error) => ({
type: GET_USERS_FAIL,
payload: error,
});
This is saga:
export function* getUsers$() {
try {
const users = yield getUsersAPI();
yield put(actions.getUsersSuccess(users.data));
} catch (error) {
yield put(actions.getUsersFail(error));
}
}
export default function* () {
yield all([takeLatest(actions.getUsers, getUsers$)]);
}
This is a reducer:
export default (state = initialState(), action) => {
const { type, payload } = action;
switch (type) {
case actions.GET_USERS:
return {
...state,
users: {
...state.users,
inProgress: true,
},
};
case actions.GET_USERS_SUCCESS:
return {
...state,
users: {
inProgress: false,
data: payload,
},
};
case actions.GET_USERS_FAIL:
return {
...state,
users: {
...state.users,
inProgress: false,
error: payload,
},
};
default:
return state;
}
};
And this is a component connected with redux:
const Home = (props) => {
useEffect(() => {
props.getUsers();
console.log('props', props.data);
}, []);
return(
<h1>Title</h1>
);
}
const mapStateToProps = ({
users: {
users: {
data
}
}
}) => ({data})
export default connect(mapStateToProps, {getUsers})(Home);
Why is this happening?
This is due to the fact that you misused the sagas in your example. As with any other effect creator as the first parameter must pass a pattern, which can be read in more detail in the documentation. The first parameter can also be passed a function, but in a slightly different way. View documentation (block take(pattern)).
In your case, you are passing a function there that will return an object
{
type: 'SOME_TYPE',
payload: 'some_payload',
}
Because of this, your worker will react to ALL events that you dispatch.
As a result, you receive data from the server, dispatch a new action to save data from the store. And besides the reducer, your getUsers saga will be called for this action too. And so on ad infinitum.
Solution
To solve this problem, just use the string constant actions.GET_USERS that you defined in your actions.
And your sagas will look like this:
export function* getUsers$() {
try {
const users = yield getUsersAPI();
yield put(actions.getUsersSuccess(users.data));
} catch (error) {
yield put(actions.getUsersFail(error));
}
}
export default function* () {
yield all([takeLatest(actions.GET_USERS, getUsers$)]);
}
This should fix your problem.
I have a simple program that retrieve some remote data and set to redux store and then display. Got an infinite loop at the end, using all kinds of methods described everywhere, just doesn't work. I believe it might be a small error, just cannot figure it out.
Component:
const OrdersScreen = (props) => {
useEffect(() => {
props.getCurrentOrders();
}, []);
return (
<>
....
</>
)
}
const mapStateToProps = (state) => {
return state;
};
const mapDispatchToProps = dispatch => {
return bindActionCreators({
getCurrentOrders
}, dispatch)
};
export default connect(mapStateToProps, mapDispatchToProps)(OrdersScreen);
Action::
export function getCurrentOrders() {
return function (dispatch, getState) {
var userID = getState().authReducer.user.local.email;
fetchServer('mGetOrders', { userID: userID })
.then((res) => {
dispatch({ type: 'GET_ORDERS', orders: res });
})
}
};
Reducer:
export default function orderReducer(state = initialState, action) {
switch (action.type) {
case 'GET_ORDERS':
return {
...state,
orders: action.orders,
}
...
Store:
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)));
I already tried everything I can find from the web, but none of them work. Really weird for me. A big thanks to anybody who can help me out of this.