I am practicing reactjs and redux course. I am understanding the react part and also redux but could not grasp the knowledge of selector and binding done in below code.
Here is the code
const reducer = (state = 1, action) => {
switch (action.type) {
case 'INCREASE':
return state + 1;
default:
return state;
}
}
const selectCounter = state => state;
const AppPresentation = ({ text, onClick }) => (
<button onClick={onClick}>{text}</button>
);
const App = connect(
(state, { bindings: { selectText } }) => ({ text: selectText(state) }),
dispatch => ({ onClick() { dispatch({ type: 'BUTTON_CLICKED' }); }})
)(AppPresentation)
const onClickIncrease = function*(){
while (yield take('BUTTON_CLICKED'))
yield put({ type: 'INCREASE' });
}
const saga = createSagaMiddleware();
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer, composeEnhancers(applyMiddleware(saga)));
saga.run(onClickIncrease);
ReactDOM.render(
<Provider store={store}>
<App bindings={{ selectText: selectCounter }} />
</Provider>,
document.querySelector('#app'));
what is the advantage of above code over below code where selector and bindings has not done?
const reducer = (state = 1, action) => {
switch (action.type) {
case 'INCREASE':
return state + 1;
default:
return state;
}
}
const selectCounter = state => state;
const AppPresentation = ({ text, onClick }) => (
<button onClick={onClick}>{text}</button>
);
const App = connect(
state => ({ text: selectCounter(state) }),
dispatch => ({ onClick() { dispatch({ type: 'BUTTON_CLICKED' }); }})
)(AppPresentation)
const onClickIncrease = function*(){
while (yield take('BUTTON_CLICKED'))
yield put({ type: 'INCREASE' });
}
const saga = createSagaMiddleware();
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer, composeEnhancers(applyMiddleware(saga)));
saga.run(onClickIncrease);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.querySelector('#app'));
Can anyone please make me understand with simple English explanation? :) Sorry my English is poor.
mapStateToProps has the follwoing syntax mapStateToProps(state, [ownProps])
If ownProps is specified as a second argument, its value will be the props passed to your component, and mapStateToProps will be additionally re-invoked whenever the component receives new props (e.g. if props received from a parent component have shallowly changed, and you use the ownProps argument, mapStateToProps is re-evaluated)
In your case { bindings: { selectText } } will receive the props passed from the parent component
In the first code you are sending a prop to the App like <App bindings={{ selectText: selectCounter }} /> which is received in you mapStateToProps component while in the second code you are not passing any such prop to the component. Hence the first has an advantage over the second to allow you to pass props to the child and update the value in the child when the prop changes.
Related
Let's say I'm having a Parent Component providing a Context which is a Store Object. For simplicity lets say this Store has a value and a function to update this value
class Store {
// value
// function updateValue() {}
}
const Parent = () => {
const [rerender, setRerender] = useState(false);
const ctx = new Store();
return (
<SomeContext.Provider value={ctx}>
<Children1 />
<Children2 />
.... // and alot of component here
</SomeContext.Provider>
);
};
const Children1 = () => {
const ctx = useContext(SomeContext);
return (<div>{ctx.value}</div>)
}
const Children2 = () => {
const ctx = useContext(SomeContext);
const onClickBtn = () => {ctx.updateValue('update')}
return (<button onClick={onClickBtn}>Update Value </button>)
}
So basically Children1 will display the value, and in Children2 component, there is a button to update the value.
So my problem right now is when Children2 updates the Store value, Children1 is not rerendered. to reflect the new value.
One solution on stack overflow is here. The idea is to create a state in Parent and use it to pass the context to childrens. This will help to rerender Children1 because Parent is rerendered.
However, I dont want Parent to rerender because in Parent there is a lot of other components. I only want Children1 to rerender.
So is there any solution on how to solve this ? Should I use RxJS to do reative programming or should I change something in the code? Thanks
You can use context like redux lib, like below
This easy to use and later if you want to move to redux you change only the store file and the entire state management thing will be moved to redux or any other lib.
Running example:
https://stackblitz.com/edit/reactjs-usecontext-usereducer-state-management
Article: https://rsharma0011.medium.com/state-management-with-react-hooks-and-context-api-2968a5cf5c83
Reducers.js
import { combineReducers } from "./Store";
const countReducer = (state = { count: 0 }, action) => {
switch (action.type) {
case "INCREMENT":
return { ...state, count: state.count + 1 };
case "DECREMENT":
return { ...state, count: state.count - 1 };
default:
return state;
}
};
export default combineReducers({ countReducer });
Store.js
import React, { useReducer, createContext, useContext } from "react";
const initialState = {};
const Context = createContext(initialState);
const Provider = ({ children, reducers, ...rest }) => {
const defaultState = reducers(undefined, initialState);
if (defaultState === undefined) {
throw new Error("reducer's should not return undefined");
}
const [state, dispatch] = useReducer(reducers, defaultState);
return (
<Context.Provider value={{ state, dispatch }}>{children}</Context.Provider>
);
};
const combineReducers = reducers => {
const entries = Object.entries(reducers);
return (state = {}, action) => {
return entries.reduce((_state, [key, reducer]) => {
_state[key] = reducer(state[key], action);
return _state;
}, {});
};
};
const Connect = (mapStateToProps, mapDispatchToProps) => {
return WrappedComponent => {
return props => {
const { state, dispatch } = useContext(Context);
let localState = { ...state };
if (mapStateToProps) {
localState = mapStateToProps(state);
}
if (mapDispatchToProps) {
localState = { ...localState, ...mapDispatchToProps(dispatch, state) };
}
return (
<WrappedComponent
{...props}
{...localState}
state={state}
dispatch={dispatch}
/>
);
};
};
};
export { Context, Provider, Connect, combineReducers };
App.js
import React from "react";
import ContextStateManagement from "./ContextStateManagement";
import CounterUseReducer from "./CounterUseReducer";
import reducers from "./Reducers";
import { Provider } from "./Store";
import "./style.css";
export default function App() {
return (
<Provider reducers={reducers}>
<ContextStateManagement />
</Provider>
);
}
Component.js
import React from "react";
import { Connect } from "./Store";
const ContextStateManagement = props => {
return (
<>
<h3>Global Context: {props.count} </h3>
<button onClick={props.increment}>Global Increment</button>
<br />
<br />
<button onClick={props.decrement}>Global Decrement</button>
</>
);
};
const mapStateToProps = ({ countReducer }) => {
return {
count: countReducer.count
};
};
const mapDispatchToProps = dispatch => {
return {
increment: () => dispatch({ type: "INCREMENT" }),
decrement: () => dispatch({ type: "DECREMENT" })
};
};
export default Connect(mapStateToProps, mapDispatchToProps)(
ContextStateManagement
);
If you don't want your Parent component to re-render when state updates, then you are using the wrong state management pattern, flat-out. Instead you should use something like Redux, which removes "state" from the React component tree entirely, and allows components to directly subscribe to state updates.
Redux will allow only the component that subscribes to specific store values to update only when those values update. So, your Parent component and the Child component that dispatches the update action won't update, while only the Child component that subscribes to the state updates. It's very efficient!
https://codesandbox.io/s/simple-redux-example-y3t32
React component is updated only when either
Its own props is changed
state is changed
parent's state is changed
As you have pointed out state needs to be saved in the parent component and passed on to the context.
Your requirement is
Parent should not re-render when state is changed.
Only Child1 should re-render on state change
const SomeContext = React.createContext(null);
Child 1 and 2
const Child1 = () => {
const ctx = useContext(SomeContext);
console.log(`child1: ${ctx}`);
return <div>{ctx.value}</div>;
};
const Child2 = () => {
const ctx = useContext(UpdateContext);
console.log("child 2");
const onClickBtn = () => {
ctx.updateValue("updates");
};
return <button onClick={onClickBtn}>Update Value </button>;
};
Now the context provider that adds the state
const Provider = (props) => {
const [state, setState] = useState({ value: "Hello" });
const updateValue = (newValue) => {
setState({
value: newValue
});
};
useEffect(() => {
document.addEventListener("stateUpdates", (e) => {
updateValue(e.detail);
});
}, []);
const getState = () => {
return {
value: state.value,
updateValue
};
};
return (
<SomeContext.Provider value={getState()}>
{props.children}.
</SomeContext.Provider>
);
};
Parent component that renders both the Child1 and Child2
const Parent = () => {
// This is only logged once
console.log("render parent");
return (
<Provider>
<Child1 />
<Child2 />
</Provider>
);
};
Now for the first requirement when you update the state by clicking button from the child2 the Parent will not re-render because Context Provider is not its parent.
When the state is changed only Child1 and Child2 will re-render.
Now for second requirement only Child1 needs to be re-rendered.
For this we need to refactor a bit.
This is where reactivity comes. As long as Child2 is a child of Provider when ever the state changes it will also gets updated.
Take the Child2 out of provider.
const Parent = () => {
console.log("render parent");
return (
<>
<Provider>
<Child1 />
</Provider>
<Child2 />
</>
);
};
Now we need some way to update the state from Child2.
Here I have used the browser custom event for simplicity. You can use RxJs.
Provider is listening the state updates and Child2 will trigger the event when button is clicked and state gets updated.
const Provider = (props) => {
const [state, setState] = useState({ value: "Hello" });
const updateValue = (e) => {
setState({
value: e.detail
});
};
useEffect(() => {
document.addEventListener("stateUpdates", updateValue);
return ()=>{
document.addEventListener("stateUpdates", updateValue);
}
}, []);
return (
<SomeContext.Provider value={state}>{props.children}</SomeContext.Provider>
);
};
const Child2 = () => {
console.log("child 2");
const onClickBtn = () => {
const event = new CustomEvent("stateUpdates", { detail: "Updates" });
document.dispatchEvent(event);
};
return <button onClick={onClickBtn}>Update Value </button>;
};
NOTE: Child2 will not have access to context
I hope this helps let me know if you didn't understand anything.
In a container, I've been using the "object shorthand" form of mapDispatchToProps to make available a single redux action (initialized as createRoutine from redux-actions) in an event handler:
const mapDispatchToProps = {
validateAddress,
}
In the handler, the action appears in this form:
function () { return dispatch(actionCreator.apply(this, arguments));}
All good. But when instead I define mapDispatchToProps as a function, so that I can add other actions that need access to dispatch, like so...
const mapDispatchToProps = (dispatch) => {
return {
validateAddress: () => dispatch(validateAddress()),
newAction1: .......,
newAction2: .......,
}
}
...my original action, validateAddress, ceases to work and appears now in this form instead:
function validateAddress() {
return dispatch(Object(_core_address_module__WEBPACK_IMPORTED_MODULE_9__["validateAddress"])());
}
I'm not sure why this is happening or how to restore the functionality of my original action. Any ideas? Thanks.
Your action creators such as validateAddress should return an object or a function that receives dispatch and getState functions for thunk actions.
Here is a working example:
const { Provider } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const initialState = {
validateAddress: 0,
};
//action types
const VALIDATE_ADDRESS = 'VALIDATE_ADDRESS';
//action creators
function validateAddress() {
return { type: VALIDATE_ADDRESS };
}
const reducer = (state, { type }) => {
if (type === VALIDATE_ADDRESS) {
return {
...state,
validateAddress: state.validateAddress + 1,
};
}
return state;
};
//selectors
const selectValidateAddress = (state) =>
state.validateAddress;
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(() => (next) => (action) =>
next(action)
)
)
);
const App = ({ validateAddress, actionCalled }) => {
return (
<button onClick={validateAddress}>
call action, called {actionCalled} times
</button>
);
};
const mapDispatchToProps = (dispatch) => {
return {
validateAddress: () => dispatch(validateAddress()),
};
};
const mapStateToProps = (state) => ({
actionCalled: selectValidateAddress(state),
});
const AppContainer = ReactRedux.connect(
mapStateToProps,
mapDispatchToProps
)(App);
ReactDOM.render(
<Provider store={store}>
<AppContainer />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<div id="root"></div>
I am getting an error on the following react redux code:
This redux index
const AllReducers = combineReducers({
foolow: follow_Reducer,
vacations: vacations_Reducer,
register: register_Reducer,
follows: follows_Reducer,
isLogged: logged_Reducer,
});
This redux reducer
const follows_Reducer = (state = false, action) => {
switch (action.type) {
case 'FOLLOW':
return !state
case 'UNFOLLOW':
return state
default:
return state;
}
}
export default follows_Reducer;
This react
export default function Vacation() {
const vacations = allvacations();
const classes = useStyles();
const [value, setValue] = React.useState('recents');
const foolow = useSelector(state => state.foolow);
const dispatch = useDispatch()
<BottomNavigation
value={value}
onClick={() => dispatch((foolow))}
onChange={handleChange} className={classes.root}
>
<BottomNavigationAction
label="Follow"
value="Follow"
icon={<FavoriteIcon />}
/>
</BottomNavigation>
}
That brings me an error
Error: Actions must be plain objects. Use custom middleware for async actions.
You are dispatching in a wrong way, dispatch should always be an object.
Try this code instead.
<BottomNavigation
value={value}
onClick={() => dispatch({ type: 'FOOLOW', payload: foolow })}
onChange={handleChange}
className={classes.root}
/>
After that you may need to do some change in the reducers too.
According to React docs :
useReducer is usually preferable to useState when you have complex
state logic that involves multiple sub-values or when the next state
depends on the previous one.
1. can somebody explain me why useReducer is not updating the state synchronously ?
const reducer = (state, action) => {
if( action.type === 'ADD_VALUE') {
console.log(`STATE IN REDUCER`, [...state, action.path]) // => ["1.1"]
return [...state, action.path]
}
}
const [state, dispatch] = useReducer(reducer, [])
<input type="button" onClick={() => {
dispatch({ type: 'ADD_VALUE', path: "1.1"})
console.log(`STATE`, state) // => []
// here i want to do some stuff based on the lastest updated state (["1.1"] and not [])
// for example dispatch an action with redux
}}/>
2. How can I do some stuff (dispatch a redux action) based on the lastest updated state (["1.1"] and not []) ?
Use useEffect to access the state correctly. You could add some safe-guarding if you want something invoking if a certain criterion is hit.
If you want to access your reducer across components, you can store the reducer using Context API. Look below for an example. You can see the reducer being injected into the Context on the parent component and then two child components that a) dispatches an action b) receives the update from the action.
1. Example of context reducer to use across multiple components
import React from "react";
import ReactDOM from "react-dom";
const Application = React.createContext({
state: null,
dispatch: null
});
function ActionComponent() {
const { dispatch } = React.useContext(Application);
return (
<div>
<div>Action Component</div>
<button onClick={() => dispatch("lol")}>Do something</button>
</div>
);
}
function ListenerComponent() {
const { state } = React.useContext(Application);
React.useEffect(
() => {
console.log(state);
},
[state]
);
return <div>Listener Component</div>;
}
function App() {
const [state, dispatch] = React.useReducer(function(state = [], action) {
return [...state, action];
});
return (
<Application.Provider value={{ state, dispatch }}>
<div className="App">
<ActionComponent />
<ListenerComponent />
</div>
</Application.Provider>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
2. Example of local reducer without using Application Context
const reducer = (state, action) => {
if( action.type === 'ADD_VALUE') {
return [...state, action.path]
}
}
const [state, dispatch] = useReducer(reducer, [])
React.useEffect(() => {
console.log(state);
}, [state]);
<input type="button" onClick={() => {
dispatch({ type: 'ADD_VALUE', path: "1.1"})
}}/>
I am new to redux - why doesn't mapStateToProps get called and the component update to show 'hello world'?
http://codepen.io/anon/pen/QyXyvW?editors=0011
const helloReducer = (state= {message:'none'}, action) => {
switch (action.type) {
case 'HELLO':
return Object.assign(state,{message:"hello world"});
default:
return state;
}
};
const myApp = Redux.combineReducers({
helloReducer
});
const App = ({onClick,message}) => (
<div>
<a href="#" onClick={onClick}>click</a><b>{message}</b>
</div>
);
const mapStateToProps = (state, ownProps) => {
return {message: state.message}
};
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onClick: () => {
dispatch({type: 'HELLO'})
}
}
}
const ConnectedApp = ReactRedux.connect(
mapStateToProps,
mapDispatchToProps
)(App);
let Provider = ReactRedux.Provider;
let store = Redux.createStore(myApp)
let e = React.render(
<Provider store={store}>
<ConnectedApp />
</Provider>,
document.getElementById('root')
);
You're assigning directly to "state" in your reducer, which is mutating it directly. You need to return Object.assign({}, state, {message:"hello world"}); instead.
Also note that React-Redux does a lot of work to make sure that a component's mapStateToProps function only runs when it absolutely has to.
replace line : return Object.assign(state,{message:"hello world"});
with this: return {...state, message:"hello world"};
It is ES6 spread operator.