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
});
}
Related
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'm having an issue with useReducer + Typescript + async. I just can't do it! When I call anything from async function it return a Promise which break my code. When I tried to get it other way, the component is doesn't re-render! That is Driving me crazy.
I wrote this issue on my personal project which represents the problem I have! https://github.com/igormcsouza/full-stack-todo/issues/15
What I can do to make it work?
I want to make a call from the backend populate the list with the information I got from backend. So my frontend need to re-render every time any change is done to the backend (when add, update or delete any registry there).
reducers.tsx
import { delete_todo, fetch_todos, insert_todo, update_todo } from
"../utils";
import { State, Actions, Todo } from "../TodoContext";
export const INITIAL_STATE: State = {
todos: [],
};
export const reducer = (state: State, action: Actions): State => {
let newState: State = {};
switch (action.type) {
case "POPULATE":
fetch_todos().then((value) => (newState = value));
return newState;
case "ADD_TODO":
if (state.todos) {
const newTodo: Todo = {
when: (+new Date()).toString(),
task: action.payload,
checked: false,
by: "Igor Souza",
};
insert_todo(newTodo);
}
fetch_todos().then((value) => (newState = value));
return newState;
case "CHECK_TODO":
action.payload.checked = !action.payload.checked;
update_todo(action.payload);
fetch_todos().then((value) => (newState = value));
return newState;
case "EDIT_TODO":
let todo = action.payload.task;
todo.task = action.payload.newTaskName;
update_todo(todo);
fetch_todos().then((value) => (newState = value));
return newState;
case "DELETE_TODO":
delete_todo(action.payload);
fetch_todos().then((value) => (newState = value));
return newState;
default:
return state;
}
};
utils.tsx (with the axios calls)
import axios from "axios";
import { State, Todo } from "./TodoContext";
// const base = "http://backend:2500";
const base = "https://full-stack-todo-bknd.herokuapp.com";
export async function fetch_todos(): Promise<State> {
let todos: State = {};
await axios
.get<State>(base + "/api/todo")
.then((response) => {
const { data } = response;
todos = data;
})
.catch((e) => console.log(e));
console.log(typeof todos.todos);
return todos;
}
export async function insert_todo(todo: Todo) {
await axios.post(base + "/api/todo", todo).catch((e) => console.log(e));
}
export async function update_todo(todo: Todo) {
await axios.put(base + "/api/todo/" + todo.id).catch((e) => console.log(e));
}
export async function delete_todo(todo: Todo) {
await axios
.delete(base + "/api/todo/" + todo.id)
.catch((e) => console.log(e));
}
context.tsx (Context APi)
import React, { createContext, useReducer } from "react";
import { reducer, INITIAL_STATE } from "./reducers";
type ContextProps = {
state: State;
dispatch: (actions: Actions) => void;
};
export interface Todo {
id?: string;
task: string;
when: string;
checked: boolean;
by: string;
}
export interface State {
todos?: Array<Todo>;
}
export interface Actions {
type: string;
payload?: any;
}
export const TodoContext = createContext<Partial<ContextProps>>({});
const TodoContextProvider: React.FC = ({ children }) => {
const [state, dispatch] = useReducer(reducer, INITIAL_STATE);
return (
<TodoContext.Provider value={{ state, dispatch }}>
{children}
</TodoContext.Provider>
);
};
export default TodoContextProvider;
Put simply, what you are trying to do is not possible. You cannot have a reducer that is asynchronous. This means that you need to move the async logic outside of the reducer itself.
The reducer is just responsible for applying the data from the action to the state. Since you are re-fetching the whole list after every action (not ideal) you only have one real action which is to replace the whole state. You would do the aysnc fetching and then refresh the state.
export const populate = (dispatch: Dispatch<Actions>) => {
fetch_todos().then((data) =>
dispatch({
type: "POPULATE",
payload: data
})
);
};
export const reducer = (state: State, action: Actions): State => {
switch (action.type) {
case "POPULATE":
return action.payload;
...
<button onClick={() => populate(dispatch)}>Populate</button>
Passing the dispatch function to an action creator is called a "thunk" and it's a popular pattern with Redux. We don't have any middleware, so we just directly call populate(dispatch) instead of something like dispatch(populate()).
Look for ways that you can streamline your code.
We can make use of the fact that all our actions call the same fetch_todos() in order to simplify things (for now -- eventually you want to not refresh the entire list after every change).
insert_todo, update_todo, and delete_todo are all extremely similar. The main difference is the axios method which can be passed as an argument with axios.request.
Though the more I look, the more I see that they should be less similar! You need to pass the todo data on your put request. You want the id property on Todo to be required and for add_todo to take Omit<Todo, 'id'>.
The inverted approach would be to make changes directly to the reducer state first. Then use a useEffect to detect changes and push the to the backend.
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.
I'm getting an object from an action (using axios) and using a map function to iterate it.
I also need to get another action but inside the parent object mapped.
I see that the request/response are ok (with returned data), but the reducer variable still gets empty.
1: component gets data
componentDidMount() {
const { match: { params } } = this.props;
this.props.getSaleDetails(params.order_id);
}
2: defining mapStateToProps and mapDispatchToProps
const mapStateToPropos = state => ({
saleDetails: state.salesOrders.saleDetails,
saleDetailFurthers: state.salesOrders.saleDetailFurthers
});
const mapDispatchToProps = dispatch =>
bindActionCreators({ getSaleDetails, getDetailFurthers }, dispatch);
3: creating a const from the redux props
const detailsArray = saleDetails.data;
4: iterate array with map function
// getDetailFurthers is another action, getting data by passing "detail_id" and updating "saleDetailFurthers" props
{detailsArray && detailsArray.map((item) => {
const {getDetailFurthers, saleDetailFurthers} = this.props;
getDetailFurthers(item.detail_id)
console.log(saleDetailFurthers)
// empty array????
count++;
return (
<Paper className={classes.paper} key={item.detail_id}>
// ... next code lines
5: Actions
export function getDetailFurthers(detail_id){
return async dispatch => {
const request = await axios.get(`${BASE_URL}/salesorders/detail/furthers/${detail_id}`)
return {
type: "SALE_DETAIL_FURTHERS_FETCHED",
payload: request
}
}
}
6: Reducers
const INITIAL_STATE = {
//... others
saleDetailFurthers: []
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
///... other cases
case "SALE_DETAIL_FURTHERS_FETCHED":
return { ...state, saleDetailFurthers: action.payload }
default:
return state
}
};
I expect the "saleDetailFurthers" const be loaded with data from redux action.
You need to use dispatch instead of returning, like so:
dispatch({
type: "SALE_DETAIL_FURTHERS_FETCHED",
payload: request
});
export function getDetailFurthers(detail_id) => dispatch =>{
const request = await axios.get(`${BASE_URL}/salesorders/detail/furthers/${detail_id}`)
dispatch ({
type: "SALE_DETAIL_FURTHERS_FETCHED",
payload: request
})
}
I am using redux with multiple reducers combined into rootReducer. How is it possible to modify the state of one reducer from another reducer? Ex:
// systemReducer.js
const INITIAL_STATE = { isLoggedIn: true }
function systemReducer(state = INITIAL_STATE, action) {
switch(action.type) { ... }
}
// messagesReducer.js
const INITIAL_STATE = { messages: [] }
function messagesReducer(state = INITIAL_STATE, action) {
switch(action.type) { ... }
}
And then say I have action makers for messagesReducer as under:
// messageActions.js
export const messagesFetchAction = (data) => {
return {
type: MESSAGES_FETCH,
data: data
}
}
Now, how can I modify systemReducer's isLoggedIn to false from messagesFetchAction()? So it looks like as under for example:
// messageActions.js
export const messagesFetchAction = (data) => {
systemState.setState({isLoggedIn: false}); // <=====
return {
type: MESSAGES_FETCH,
data: data
}
}
If you have two reducers does not mean that you have several stores. You still have single store, but combined from two reducers. In general, your store may look like:
{
systemReducer: {
isLoggedIn: true
},
messagesReducer: {
messages: []
}
}
You couldn't dispatch action from reducer. This is prohibited by redux. But you may dispatch several actions from action creator. For example:
export const messagesFetchAction = (data) => (dispatch) => {
dispatch({type: LOGGED_IN, isLoggedIn: false});
dispatch({
type: MESSAGES_FETCH,
data: data
});
}
The action creator above is for Redux Thunk. To be able to use it, apply middleware when creating store like this
const rootReducer = combineReducers({
systemReducer,
messagesReducer
});
const store = createStore(rootReducer, applyMiddleware(
thunkMiddleware
));