Given that in a file called goose.js I have exported a reducer as such:
//reducer
const reducer = handleActions(
{
[setExchangeRate]: (state, { exchangeRate }) => ({
...state,
exchangeRate
}),
[setExchangeCurrency]: (state, { exchangeCurrency }) => ({
...state,
exchangeCurrency
}),
[setBaseCurrency]: (state, { baseCurrency }) => ({
...state,
baseCurrency
})
},
initialState
);
export default reducer;
const selectCurrencyExchange = state => state[MODULE_NAME];
and in another file called index.jas, I try to import the reducer and assigned it to a provider:
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import reducer from "./redux/goose";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={reducer}>
<App />
</Provider>,
rootElement
);
is there a reason why I am getting
TypeError store.getState is not a function error? Is it illegal to do this?
The dependencies I am using is react 16.8.2, react-redux 5.07, redux, redux actions, redux-define.
I welcome an example that allows me to use the reducer structure in goose.js
react-redux Provider is taking a store not a reducer.
You need to create the store with use of createStore function from redux library and pass to it your reducer. And after that you can pass the created store to the Provider.
Have a look at the basic example in redux documentation.
Related
I have a unique situation in which my <Provider> component (and entire Redux Store) is exported from a middleware application to multiple front end React apps. The middleware has its own set of reducers but the client apps can inject their own reducers into the store when they call the provider.
It is now being asked that I accept an initial state (preloadedState) object when the Provider is called so that the initial state of the app can be loaded with dynamic initial state. This object will be an arbitrary set of state data (with corresponding reducers) so I'll have the data structures correctly with the shape in the reducers, but I won't know what values they're sending.
Here's the basic ReduxStore set up, changed here for simplicity:
ReduxStore.ts
import { configureStore } from '#reduxjs/toolkit';
import { ExampleReducer } from './slices/ExampleSlice';
export const reducer = {
example: ExampleReducer
};
const ReduxStore = configureStore({
middleware: ...,
reducer,
});
export default ReduxStore;
CoreProvider.tsx
import React from 'react';
import { Provider } from 'react-redux';
import { combineReducers, ReducersMapObject, AnyAction } from '#reduxjs/toolkit';
import ReduxStore, { reducer } from './ReduxStore';
export type ConfigProps = {
newReducers?: ReducersMapObject<unknown, AnyAction>;
preloadedState: Object;
};
const CoreProvider: React.FC<ConfigProps> = ({
children,
newReducers,
}) => {
const newReducer = combineReducers({ ...newReducers, ...reducer });
ReduxStore.replaceReducer(newReducer);
return <Provider store={ReduxStore}>{children}</Provider>;
};
export default CoreProvider;
index.jsx
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<CoreProvider newReducers={{ arbitrary: arbitraryReducer }}>
<App />
</CoreProvider>
</React.StrictMode>,
);
Now, I'm aware of the reasons I should NOT do this, and I am aware that initial state comes from the reducers themselves, which is handled when the reducers are merged with the default reducers when the Provider is called.
I know that configureStore accepts a preloadedState option but where I'm stuck is how to dynamically pass a preloadedState coming in as a prop from the Provider component. I have tried wrapping the configureStore call inside a function that is called within the Provider component, which does indeed set the initial state, but my ReduxStore export within ReduxStore.ts is undefined due to the way the app loads and the configureStore is called.
Provider calling configureStore with preloadedState props:
import { initReduxStore, reducer } from './ReduxStore';
const CoreProvider: React.FC<ConfigProps> = ({
children,
newReducers,
preloadedState
}) => {
const newReducer = combineReducers({ ...newReducers, ...reducer });
const ReduxStore = initReduxStore(preloadedState, newReducer);
return <Provider store={ReduxStore}>{children}</Provider>;
};
export default CoreProvider;
ReduxStore with exported configureStore function and undefined ReduxStore export:
export const reducer = {
example: ExampleReducer
};
let ReduxStore;
export const initReduxStore = (incomingState, incomingReducer) => {
if (ReduxStore) return ReduxStore;
const store = configureStore({
middleware: ...,
preloadedState: incomingState,
reducer: incomingReducer,
});
ReduxStore = store;
return ReduxStore;
};
export default ReduxStore; // This becomes undefined because the file loads before the Provider component calls the initReduxStore function
The ReduxStore export is undefined because the file loads before the Provider component calls the initReduxStore function and ReduxStore is undefined at the time it's exported.
Is there a known way that I have overlooked to easily set the preloaded state object when the provider is called? Should I restructure how the ReduxStore is created?
In short terms, how do I get the preloadedState prop from my Provider to my configureStore, while still exporting the ReduxStore to the rest of the app?
export const ReduxStore = configureStore({
preloadedState,
reducer,
});
// HOW DO I CONNECT THESE
const CoreProvider: React.FC<ConfigProps> = ({
children,
newReducers,
preloadedState
}) => {
return <Provider store={store}>{children}</Provider>;
};
export default CoreProvider;
Any thoughts are appreciated.
The short answer is, you can't. The preloadedState option can only be passed in when you call configureStore(). Once the store is created, the only way to update the store state is to dispatch an action.
On top of that, behavior such as replacing the reducer or dispatching actions would qualify as a side effect, and React components must not have side effects directly in the rendering logic.
The closest suggestion I have would be a useLayoutEffect hook in this component that watches for changes to the provided reducers or state, and does the store.replaceReducer() call.
Also, you could have a wrapping reducer that watches for some kind of a "merge in this additional state" action, and returns the updated state with the additional fields.
But overall, this is a very unusual use case, and not something Redux is really designed for.
Here's my code. I want to fetch ingredients and totalPrice from redux state but when I fetch it in main app, it is giving my undefined (this.props.ingredients and this.props.totalPrice).
reducer.js
const initialState = {
ingredients: {
salad: 0,
bacon: 0,
cheese: 0,
meat: 0,
},
totalPrice: 4.0
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD_INGREDIENTS':
break;
default:
return state;
}
};
export default reducer
Main App (Connecting with app)
......
const mapStateToProps = state => {
return {
ings: state.ingredients,
tp: state.totalPrice
}
}
export default connect(mapStateToProps)(Burgerbuilder, axios)
This is giving me undefined
console.log(this.props.ings)
console.log(this.props.tp)
Here's how it connected it with store
import {Provider} from 'react-redux'
import {createStore} from 'redux'
import reducer from './redux/reducer';
const store = createStore(reducer)
const app = (
<React.StrictMode>
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
</React.StrictMode>
)
A reducer must always return a complete state. Inside case "ADD_INGREDIENTS": you break and don't return anything. So calling an action with { type: "ADD_INGREDIENTS" } will cause your whole state to be undefined. You need to return state.
Additionally, I don't understand what your are trying to do by including axios in your connect. It should be connect(mapStateToProps)(Burgerbuilder).
You did not call combineReducer function.
In your reducers directory create a file called index.js for example, then add this code to it
import ingredientsReducer from './reducer'
export default combineReducers(
{
ingredients: ingredientsReducer,
}
);
then in your main.js file, when you create a store, feed this exported combineReducers result to it.
You can read more about combineReducers here
I created a create react app and included redux with card lists and a searchbox that displayed the filtered results, the app was working before I added redux but now it isn't returning any results. When I console.log(this.props.store) it is returning undefined. I would really appreciate it if someone can help me with this. My files are as below:
constants.js
export const CHANGE_SEARCH_FIELD = 'CHANGE_SEARCH_FIELD';
actions.js
import {CHANGE_SEARCH_FIELD} from './constants.js';
export const setSearchField = (text) => ({
type: CHANGE_SEARCH_FIELD,
payload: text
})
reducer.js
import {CHANGE_SEARCH_FIELD} from './constants.js';
const intialState = {
searchField: ''
}
export const searchTeacher = (state=intialState, action={}) => {
switch(action.type) {
case CHANGE_SEARCH_FIELD:
return Object.assign({}, state, { searchField: action.payload });
default:
return state;
}
}
index.js
import ReactDOM from 'react-dom';
import './index.css';
import {Provider} from 'react-redux';
import {createStore} from 'redux';
import App from './App.js'; //Our main parent component
import {searchTeacher} from './reducer.js';
import 'tachyons';
import * as serviceWorker from './serviceWorker';
const store = createStore(searchTeacher)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root') );
serviceWorker.unregister();
App.js
import React, {Component} from 'react';
import {connect} from 'react-redux';
import CardList from './CardList.js';
import {teacher} from './teacher.js';
import Searchbox from './searchbox.js';
import ErrorBoundry from './ErrorBoundry';
import Scroll from './Scroll.js';
import './App.css';
import {setSearchField} from './actions.js';
const mapStateToProps = state => {
return {
searchField: state.searchField
}
}
const mapDispatchToProps = (dispatch) => {
return {
onSearchChange: (event) => dispatch(setSearchField(event.target.value))
}
}
class App extends Component {
constructor(){
super()
this.state = {
teacher: teacher, //teacher: [],
}
}
render(){
console.log(this.props.store);
const { searchField, onSearchchange } = this.props;
const filteredteacher= teacher.filter(
teacher =>{
return teacher.name.toLowerCase().includes(searchField.toLowerCase());
});
return(
<div className="tc">
<h1 className="f1"> Faculty Members ! </h1>
<Searchbox searchChange={onSearchchange} />
<Scroll>
<ErrorBoundry>
<CardList teacher={filteredteacher} />
</ErrorBoundry>
</Scroll>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
There won't be any props.store, because none of your code is passing down a prop named store to that component.
Components that have been wrapped in connect get props from three sources, combined:
Props passed from the parent component
Props returned from mapState
Props returned from mapDispatch
In this case, mapState is returning {searchField}, and mapDispatch is returning {onSearchChange}, and there's no props from the parent. So, the combined props are {searchField, onSearchChange}.
As a side note, you should use the "object shorthand" form of mapDispatch instead of writing it as a function:
const mapDispatch = {onSearchChange: setSearchField};
You will get two props from redux according to your code,
this.props.searchField
this.props.onSearchChange
connect function of react-redux used to connect react and redux.
mapDispatch is used to dispatch your actions which hold the payload(Second argument of connect function)
mapState is used to get the state of your properties(First argument of connect function)
So in your code, there is not any prop named store, Store is a global redux state which you can get with this method Store.getState() but here is store is redux store which you are passing here const store = createStore(searchTeacher) in your index.js file, This will show whole state of the redux store.
here is how you can get the state of your store.
How do I access store state in React Redux?
You will dispatch an action named onSearchChange like below in your on change method.
this.props.onSearchChange(e)
and redux will return you a value of this after storing in reducer with the name of this.props.searchField.
this.props.store would only be accessible if it was passed down from a parent component (which you are not doing here)
You create your store in index.js but you are not exposing an interface to it.
const store = createStore(searchTeacher);
You can expose these functions from your index.js file to reference the store:
export const getStore = () => store;
export const getState = () => { return store.getState(); };
Then from anywhere else (although not good practice):
import { getStore, getState } from 'index.js';
I'm a bit confused and would love an answer that will help me to clear my thoughts.
Let's say I have a backend (nodejs, express etc..) where I store my users and their data, and sometimes I wanna fetch data from the backend, such as the user info after he logs in, or a list of products and save them in the state.
My approach so far and what I've seen, I fetch the data before the component loads and dispatch an action with the data from the response.
But I recently started digging a bit about this and I saw react-thunk library which I knew earlier and started to wonder if what is the best practice of fetching from backend/API?
Has React Hooks change anything about this topic? Is it important to know this?
I feel a bit dumb but couldn't find an article or video that talks exactly about this topic :)
To do this best practice, use the following method:
I used some packages and patterns for best practice:
redux-logger for log actions and states in console of browser.
reselect Selectors can compute derived data, allowing Redux to
store the minimal possible state and etc.
redux-thunk Thunks are the recommended middleware for basic
Redux side effects logic, including complex synchronous logic that
needs access to the store, and simple async logic like AJAX requests
and etc.
axios for work with api (Promise based HTTP client for the
browser and node.js)
create a directory by name redux or any name of you like in src folder and
then create two files store.js and rootReducer.js in redux directory. We assume fetch products from API.
To do this:
Create a new directory by name product in redux directory and then
create four files by names product.types.js, product.actions.js,
product.reducer.js, product.selector.js in redux/product directory
The structure of the project should be as follows
...
src
App.js
redux
product
product.types.js
product.actions.js
product.reducer.js
rootReducer.js
store.js
Index.js
package.json
...
store.js
In this file we do the redux configuration
// redux/store.js:
import { createStore, applyMiddleware } from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk";
import rootReducer from "./root-reducer";
const middlewares = [logger, thunk];
export const store = createStore(rootReducer, applyMiddleware(...middlewares));
rootReducer.js
The combineReducers helper function turns an object whose values are
different reducing functions into a single reducing function you can
pass to createStore.
// redux/rootReducer.js
import { combineReducers } from "redux";
import productReducer from "./product/product.reducer";
const rootReducer = combineReducers({
shop: productReducer,
});
export default rootReducer;
product.types.js
In this file we define constants for manage types of actions.
export const ShopActionTypes = {
FETCH_PRODUCTS_START: "FETCH_PRODUCTS_START",
FETCH_PRODUCTS_SUCCESS: "FETCH_PRODUCTS_SUCCESS",
FETCH_PRODUCTS_FAILURE: "FETCH_PRODUCTS_FAILURE"
};
product.actions.js
In this file we create action creators for handle actions.
// redux/product/product.actions.js
import { ShopActionTypes } from "./product.types";
import axios from "axios";
export const fetchProductsStart = () => ({
type: ShopActionTypes.FETCH_PRODUCTS_START
});
export const fetchProductsSuccess = products => ({
type: ShopActionTypes.FETCH_PRODUCTS_SUCCESS,
payload: products
});
export const fetchProductsFailure = error => ({
type: ShopActionTypes.FETCH_PRODUCTS_FAILURE,
payload: error
});
export const fetchProductsStartAsync = () => {
return dispatch => {
dispatch(fetchProductsStart());
axios
.get(url)
.then(response => dispatch(fetchProductsSuccess(response.data.data)))
.catch(error => dispatch(fetchProductsFailure(error)));
};
};
product.reducer.js
In this file we create productReducer function for handle actions.
import { ShopActionTypes } from "./product.types";
const INITIAL_STATE = {
products: [],
isFetching: false,
errorMessage: undefined,
};
const productReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case ShopActionTypes.FETCH_PRODUCTS_START:
return {
...state,
isFetching: true
};
case ShopActionTypes.FETCH_PRODUCTS_SUCCESS:
return {
...state,
products: action.payload,
isFetching: false
};
case ShopActionTypes.FETCH_PRODUCTS_FAILURE:
return {
...state,
isFetching: false,
errorMessage: action.payload
};
default:
return state;
}
};
export default productReducer;
product.selector.js
In this file we select products and isFetching from shop state.
import { createSelector } from "reselect";
const selectShop = state => state.shop;
export const selectProducts = createSelector(
[selectShop],
shop => shop.products
);
export const selectIsProductsFetching = createSelector(
[selectShop],
shop => shop.isFetching
);
Index.js
In this file wrapped whole app and components with Provider for access child components to the store and states.
// src/Index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import { store } from "./redux/store";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
App.js class component
In this file we do connect to the store and states with class component
// src/App.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
selectIsProductsFetching,
selectProducts
} from "./redux/product/product.selectors";
import { fetchProductsStartAsync } from "./redux/product/product.actions";
class App extends Component {
componentDidMount() {
const { fetchProductsStartAsync } = this.props;
fetchProductsStartAsync();
}
render() {
const { products, isProductsFetching } = this.props;
console.log('products', products);
console.log('isProductsFetching', isProductsFetching);
return (
<div className="App">Please see console in browser</div>
);
}
}
const mapStateToProps = createStructuredSelector({
products: selectProducts,
isProductsFetching: selectIsProductsFetching,
});
const mapDispatchToProps = dispatch => ({
fetchProductsStartAsync: () => dispatch(fetchProductsStartAsync())
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
or App.js with functional component ( useEffect hook )
In this file we do connect to the store and states with functional component
// src/App.js
import React, { Component, useEffect } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
selectIsProductsFetching,
selectProducts
} from "./redux/product/product.selectors";
import { fetchProductsStartAsync } from "./redux/product/product.actions";
const App = ({ fetchProductsStartAsync, products, isProductsFetching}) => {
useEffect(() => {
fetchProductsStartAsync();
},[]);
console.log('products', products);
console.log('isProductsFetching', isProductsFetching);
return (
<div className="App">Please see console in browser</div>
);
}
const mapStateToProps = createStructuredSelector({
products: selectProducts,
isProductsFetching: selectIsProductsFetching,
});
const mapDispatchToProps = dispatch => ({
fetchProductsStartAsync: () => dispatch(fetchProductsStartAsync())
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
It is my first application using react context with hooks instead of react-redux and would like to get help of the structure of the application.
(I'm NOT using react-redux or redux-saga libraries.)
Context
const AppContext = createContext({
client,
user,
});
One of actions example
export const userActions = (state, dispatch) => {
function getUsers() {
dispatch({ type: types.GET_USERS });
axios
.get("api address")
.then(function(response) {
dispatch({ type: types.GOT_USERS, payload: response.data });
})
.catch(function(error) {
// handle error
});
}
return {
getUsers,
};
};
Reducer (index.js): I used combineReducer function code from the redux library
const AppReducer = combineReducers({
client: clientReducer,
user: userReducer,
});
Root.js
import React, { useContext, useReducer } from "react";
import AppContext from "./context";
import AppReducer from "./reducers";
import { clientActions } from "./actions/clientActions";
import { userActions } from "./actions/userActions";
import App from "./App";
const Root = () => {
const initialState = useContext(AppContext);
const [state, dispatch] = useReducer(AppReducer, initialState);
const clientDispatch = clientActions(state, dispatch);
const userDispatch = userActions(state, dispatch);
return (
<AppContext.Provider
value={{
clientState: state.client,
userState: state.user,
clientDispatch,
userDispatch,
}}
>
<App />
</AppContext.Provider>
);
};
export default Root;
So, whenever the component wants to access the context store or dispatch an action, this is how I do from the component :
import React, { useContext } from "react";
import ListMenu from "../common/ListMenu";
import List from "./List";
import AppContext from "../../context";
import Frame from "../common/Frame";
const Example = props => {
const { match, history } = props;
const { userState, userDispatch } = useContext(AppContext);
// Push to user detail route /user/userId
const selectUserList = userId => {
history.push(`/user/${userId}`);
userDispatch.clearTabValue(true);
};
return (
<Frame>
<ListMenu
dataList={userState.users}
selectDataList={selectUserList}
/>
<List />
</Frame>
);
};
export default Example;
The problem I faced now is that whenever I dispatch an action or try to access to the context store, the all components are re-rendered since the context provider is wrapping entire app.
I was wondering how to fix this entire re-rendering issue (if it is possible to still use my action/reducer folder structure).
Also, I'm fetching data from the action, but I would like to separate this from the action file as well like how we do on redux-saga structure. I was wondering if anybody know how to separate this without using redux/redux-saga.
Thanks and please let me know if you need any code/file to check.
I once had this re-rendering issue and I found this info on the official website:
https://reactjs.org/docs/context.html#caveats
May it will help you too
This effect (updating components on context update) is described in official documentation.
A component calling useContext will always re-render when the context value changes. If re-rendering the component is expensive, you can optimize it by using memoization
Possible solutions to this also described
I see universal solution is to useMemo
For example
const Example = props => {
const { match, history } = props;
const { userState, userDispatch } = useContext(AppContext);
// Push to user detail route /user/userId
const selectUserList = userId => {
history.push(`/user/${userId}`);
userDispatch.clearTabValue(true);
};
const users = userState.users;
return useMemo(() => {
return <Frame>
<ListMenu
dataList={users}
selectDataList={selectUserList}
/>
<List />
</Frame>
}, [users, selectUserList]); // Here are variables that component depends on
};
I also may recommend you to completly switch to Redux. You're almost there with using combineReducers and dispatch. React-redux now exposes useDispatch and useSelector hooks, so you can make your code very close to what you're doing now (replace useContext with useSelector and useReducer with useDispatch. It will require minor changes to arguments)