Hi I'm trying Redux recently. I wanted to build a counter for practice.
It's composed of two buttons(plus & minus) and a counter showing the current number.
The action creators and reducers are connected to the corresponding components. The store is also connected to the provider. Yet I don't know why but the initial number(state passed as props to the counter component) is not showing?
Codepen
Javascript:
/*--Reducers--*/
const reducer_num = (state=0,action)=>{
switch(action.type){
case "PLUS":
case "MINUS":
return state + action.payload;
default:
return state;
}
}
const rootReducer = Redux.combineReducers({
num: reducer_num
});
/*--Action creators--*/
const action_plus = ()=>{
return {
type: "PLUS",
payload: 1
};
}
const action_minus = ()=>{
return {
type: "MINUS",
payload: -1
};
}
/*--Components & containers--*/
//counter
const Counter = (props)=>{
return (<div className="counter">{props.num}</div>);
}
const mapStateToProps=(state)=>{
return {num: state.num};
}
ReactRedux.connect(mapStateToProps)(Counter);
//plus
const Plus = (props)=>{
return (<div className="plus" onClick={props.plus}>+</div>);
}
const mapDispatchToProps_plus = (dispatch)=>{
return Redux.bindActionCreators({plus: action_plus},dispatch);
}
ReactRedux.connect(mapDispatchToProps_plus)(Plus);
//minus
const Minus = (props)=>{
return (<div onClick={props.minus} className="minus">-</div>);
}
const mapDispatchToProps_minus = (dispatch)=>{
return Redux.bindActionCreators({minus: action_minus},dispatch);
}
ReactRedux.connect(mapDispatchToProps_minus)(Minus);
// Provider & store
const Provider = ReactRedux.Provider,
store = Redux.createStore(rootReducer);
//App
const App = ()=>{
return (
<Provider store={store}>
<div className="container">
<Plus/>
<Counter/>
<Minus/>
</div>
</Provider>
);
}
ReactDOM.render(<App/>,document.body);
Many thanks for any help.
There were a few issues with your code,
First: mapDispatchToProps is second argument to connect function and when you don't have a mapStateToProps function, you need to pass the first argument as null
Second: connect returns a Component connected to the store that you need to make use of but you are not using that
Complete Code:
/*--Reducers--*/
const reducer_num = (state=0,action)=>{
switch(action.type){
case "PLUS":
case "MINUS":
console.log(action.type)
return state + action.payload;
default:
return state;
}
}
const rootReducer = Redux.combineReducers({
num: reducer_num
});
/*--Action creators--*/
const action_plus = ()=>{
return {
type: "PLUS",
payload: 1
};
}
const action_minus = ()=>{
console.log('minus')
return {
type: "MINUS",
payload: -1
};
}
/*--Components & containers--*/
//counter
let Counter = (props)=>{
return (<div className="counter">{props.num}</div>);
}
const mapStateToProps=(state)=>{
return {num: state.num};
}
Counter = ReactRedux.connect(mapStateToProps)(Counter);
//plus
let Plus = (props)=>{
return (<div className="plus" onClick={props.plus}>+</div>);
}
const mapDispatchToProps_plus = (dispatch)=>{
return Redux.bindActionCreators({plus: action_plus},dispatch);
}
Plus = ReactRedux.connect(null,mapDispatchToProps_plus)(Plus);
//minus
let Minus = (props)=>{
console.log(props);
return (<div onClick={props.minus} className="minus">-</div>);
}
const mapDispatchToProps_minus = (dispatch)=>{
return Redux.bindActionCreators({minus: action_minus},dispatch);
}
Minus=ReactRedux.connect(null,mapDispatchToProps_minus)(Minus);
// Provider & store
const Provider = ReactRedux.Provider,
store = Redux.createStore(rootReducer);
//App
const App = ()=>{
return (
<Provider store={store}>
<div className="container">
<Plus/>
<Counter/>
<Minus/>
</div>
</Provider>
);
}
ReactDOM.render(<App/>,document.body);
CODEPEN
You code of Counter component should look like this:
let Counter = (props)=>{
return (<div className="counter">{props.num}</div>);
}
Counter = ReactRedux.connect(mapStateToProps)(Counter);
You have to render component returned by the connect function. Similar mistake is in Plus and Minus.
You have provided mapDispatchToProps_minus as the first argument for Plus and Minus component which is wrong. First argument should be mapStateToProps
const mapStateToProps = () => ({})
ReactRedux.connect(mapStateToProps, mapDispatchToProps_minus)(Minus);
I m not sure but i just used your Codepen and it seems that you forgot to pass props to your counter component.
if you do
Counter num={4}
then it should work :)
You create connected components but you never assign them to variables, and you put the old 'dumb' components in the main component that you render to the DOM.
I've created a fixed version of your CodePen.
/*--Reducers--*/
const reducer_num = (state = 0, action) => {
switch (action.type) {
case "PLUS":
case "MINUS":
return state + action.payload;
default:
return state;
}
};
const rootReducer = Redux.combineReducers({
num: reducer_num
});
/*--Action creators--*/
const action_plus = () => {
return {
type: "PLUS",
payload: 1
};
};
const action_minus = () => {
return {
type: "MINUS",
payload: -1
};
};
/*--Components & containers--*/
//counter
const Counter = props => {
return <div className="counter">{props.num}</div>;
};
const mapStateToProps = state => {
return { num: state.num };
};
const CounterContainer = ReactRedux.connect(mapStateToProps)(Counter);
//plus
const Plus = props => {
return <div className="plus" onClick={props.plus}>+</div>;
};
const mapDispatchToProps_plus = dispatch => {
return Redux.bindActionCreators({ plus: action_plus }, dispatch);
};
const PlusContainer = ReactRedux.connect(mapDispatchToProps_plus)(Plus);
//minus
const Minus = props => {
return <div onClick={props.minus} className="minus">-</div>;
};
const mapDispatchToProps_minus = dispatch => {
return Redux.bindActionCreators({ minus: action_minus }, dispatch);
};
const MinusContainer = ReactRedux.connect(mapDispatchToProps_minus)(Minus);
// Provider & store
const Provider = ReactRedux.Provider,
store = Redux.createStore(rootReducer);
//App
const App = () => {
return (
<Provider store={store}>
<div className="container">
<PlusContainer />
<CounterContainer />
<MinusContainer />
</div>
</Provider>
);
};
ReactDOM.render(<App />, document.body);
Related
I have a variable const foo: Array<ObjectExt> = useSelector(fooSelector); in a functional component. I want a copy of this variable from the first time the component is loaded that does not change when foo does.
When working with class components, I could simply have const fooCopy = foo.slice(); but that does not work here since the component reloads every time and fooCopy changes.
How do I achieve this in a functional component?
Just useState with the initial value as a copy of foo.
const foo : Array<ObjectExt> = useSelector(fooSelector);
const [origFoo] = useState(foo.slice());
Once origFoo has been initialized, it won't be re-initialized on rerender. You can destructure the setter out if you need to update its value later:
const [origFoo, setOrigFoo] = useState(foo);
// ...
if(someCondition) setOrigFoo(foo.slice())
const {useState} = React;
function App() {
const foo = [new Date().getTime()];
const [origFoo] = useState(foo.slice());
// Just so we have a way to force a rerender
const [count, setCount] = React.useState(0);
return (
<div>
<p>{JSON.stringify(foo)} </p>
<p>{JSON.stringify(origFoo)}</p>
<button onClick={() => setCount(count + 1)}>Update</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<App />,
rootElement
);
<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>
<div id="root"></div>
One solution is to set a flag in local component state. If that flag is fals, then make a copy of the value. Otherwise, don't.
The solution I'd use to accomplish this functionality, is to make a copy of foo, something like initialFoo in the store, and pick it in needed components.
I want a copy of this variable from the first time the component is loaded that does not change when foo does.
When you use useSelector(selector) then react-redux will run selector every time the state changes, if the return value is different than then last time it ran then react-redux will re render the component.
The easiest way of doing this is using a selector that returns only the value it got when called the first time:
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const initialState = { count: 0 };
//action types
const ADD = 'ADD';
//action creators
const add = () => ({
type: ADD,
});
const reducer = (state, { type, payload }) => {
if (type === ADD) {
return { ...state, count: state.count + 1 };
}
return state;
};
//selectors
const selectCount = (state) => state.count;
//function returning a function
const createSelectInitialCount = () => {
//initialize NONE when createSelectInitialCount is called
const NONE = {};
let last = NONE;
//return the selector
return (state) => {
//check if last value was set
if (last === NONE) {
//set last value (only when called the first time)
last = selectCount(state);
}
return last;
};
};
//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 InitialFoo = React.memo(function InitialFoo(props) {
const selectInitialCount = React.useMemo(//can also use useCallback
createSelectInitialCount,//createSelectInitialCount() if you use useCallback
[]
);
const foo = useSelector(selectInitialCount);
return (
<div>
<h3>initialfoo</h3>
<pre>
{JSON.stringify({ ...props, foo }, undefined, 2)}
</pre>
</div>
);
});
const App = () => {
const foo = useSelector(selectCount);
const dispatch = useDispatch();
const [other, setOther] = React.useState(0);
const [showFoo, setShowFoo] = React.useState(true);
const remountFoo = () => {
setShowFoo(false);
Promise.resolve().then(() => setShowFoo(true));
};
return (
<div>
<button onClick={() => dispatch(add())}>
foo:{foo}
</button>
<button onClick={() => setOther((o) => o + 1)}>
other{other}
</button>
<button onClick={remountFoo}>remount Foo</button>
{showFoo && <InitialFoo other={other} />}
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</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>
Another way is to create a pure component using React.memo with a custom compare function that ignores foo:
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const initialState = { count: 0 };
//action types
const ADD = 'ADD';
//action creators
const add = () => ({
type: ADD,
});
const reducer = (state, { type, payload }) => {
if (type === ADD) {
return { ...state, count: state.count + 1 };
}
return state;
};
//selectors
const selectCount = (state) => state.count;
//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 InitialFoo = React.memo(
function InitialFoo(props) {
return (
<div>
<h3>initialfoo</h3>
<pre>{JSON.stringify(props, undefined, 2)}</pre>
</div>
);
},
//custom compare function when returning false
// component will re render
({ other }, { other: newOther }) => {
return other === newOther;
}
);
const InitialFooContainer = (props) => {
const foo = useSelector(selectCount);
//store foo in ref, will never change after mount
const fooRef = React.useRef(foo);
const newProps = { ...props, foo: fooRef.current };
return <InitialFoo {...newProps} />;
};
const App = () => {
const foo = useSelector(selectCount);
const dispatch = useDispatch();
const [other, setOther] = React.useState(0);
const [showFoo, setShowFoo] = React.useState(true);
const remountFoo = () => {
setShowFoo(false);
Promise.resolve().then(() => setShowFoo(true));
};
return (
<div>
<button onClick={() => dispatch(add())}>
foo:{foo}
</button>
<button onClick={() => setOther((o) => o + 1)}>
other{other}
</button>
<button onClick={remountFoo}>remount Foo</button>
{showFoo && (
<InitialFooContainer foo={foo} other={other} />
)}
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</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 have to use useDispatch() for my toggle buttons so I have to refractor them from react to redux state. I was following the tutorial of basics of Redux and I think I have done that properly but when I try to at least useSelector to display the redux'state of button it doesnt show anything.
So here is my code:
// types.js in actions folder
export const TOGGLE = "TOGGLE";
// buttonActions in actions folder
export const toggle = () => {
return {
type: 'TOGGLE'
};
};
// buttonReducer in reducers folder
const buttonReducer = (state = true, action) => {
switch(action.type) {
case 'TOGGLE':
return !state;
default:
return state;
};
};
export default buttonReducer;
And the buttonReducer is imported into combineReducers which go to store.
The component code:
import React, { useState, useEffect } from 'react'
import isloff from './mainpage_imgs/isloff.png'
import islon from './mainpage_imgs/islon.png'
import PropTypes from "prop-types";
import { connect, useDispatch, useSelector } from "react-redux";
import { toggle } from '../../actions/buttonActions'
const Islbutton = props => {
const [open, setOpen] = useState(true);
const [role, setRole] = useState('');
useEffect(() => {
if (props.auth.user)
{
setRole(props.auth.user.role);
}
}, []);
const test = useSelector(state => state.button);
const checkRole = (role) => {
if (role === 'Menager' || role === 'Technolog')
{
return true }
else
{
return false
};
}
const toggleImage = () => {
if(checkRole(role)) {
setOpen(!open)
};
}
const getImageName = () => open ? 'islOnn' : 'islOfff'
const dispatch = useDispatch();
return(
<div>
<img style={islplace} src={open ? islon : isloff }
onClick={()=> dispatch(toggle())} />
</div>
);
}
Islbutton.propTypes = {
button: PropTypes.func.isRequired,
auth: PropTypes.obj.isRequired
};
const mapStateToProps = state => ({
button: state.button,
auth: state.auth
});
export default connect(mapStateToProps, {}), (Islbutton);
Based on your latest comments and my understanding of your use case I may suggest following distilled approach:
//dependencies
const { render } = ReactDOM,
{ createStore } = Redux,
{ connect, Provider } = ReactRedux
//action creators
const SET_ROLE = 'SET_ROLE',
MANAGER_APPROVED = 'MANAGER_APPROVED',
setRole = role => ({type:SET_ROLE, role}),
mngAppr = () => ({type:MANAGER_APPROVED})
//initial state, reducer, store
const initialState = {role:'Technolog', approved:false},
appReducer = (state=initialState, action) => {
switch(action.type){
case SET_ROLE : {
const {role} = state,
{role: newRole} = action
return {...state, role: newRole}
}
case MANAGER_APPROVED : {
const {approved} = state
return {...state, approved: !approved}
}
default: return state
}
},
store = createStore(appReducer)
//ui component to emulate toggling roles
const SwitchRoles = ({currentRole, switchRole}) => (
<div>
<label><input type="radio" name="role" value="Manager" onChange={e => switchRole(e.target.value)} />Manager</label>
<label><input type="radio" name="role" value="Technolog" onChange={e => switchRole(e.target.value)} />Technolog</label>
</div>
)
//connect radio buttons click to togling roles action
const mapDispatch = dispatch => ({switchRole: role => dispatch(setRole(role))}),
SwitchRolesContainer = connect(null,mapDispatch)(SwitchRoles)
//ui component to toggle 'approved' within global state
const ToggleApprove = ({onApprove,isManager}) => (
<button onClick={onApprove} disabled={!isManager}>Toggle</button>
)
//connect onToggle handler to dispatching 'toggle' action
const mapStateToProps = ({role}) => ({isManager: role == 'Manager'}),
mapDispatchToProps = dispatch => ({onApprove: () => dispatch(mngAppr())}),
ToggleApproveContainer = connect(mapStateToProps, mapDispatchToProps)(ToggleApprove)
//ui component to display current state of 'open'
const IsApproved = ({isApproved}) => <div>{isApproved ? 'Approved by manager' : 'Not approved by manager'}</div>
//attach isOpen prop to global 'open' variable
const mapState = ({approved}) => ({isApproved: approved}),
IsApprovedContainer = connect(mapState)(IsApproved)
//render the app
render (
<Provider store={store}>
<SwitchRolesContainer />
<IsApprovedContainer />
<ToggleApproveContainer />
</Provider>,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/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.1.3/react-redux.min.js"></script><div id="root"></div>
Hopefully, it gives a piece of mind about toggling global variables and mapping their values onto local components state.
I am using hooks and context api.I have multiple actions that write them into seperate file.my problem this:in another file how can I access state?
I use this file for create my contexts:
createContext.js
import React, { useReducer } from "react";
export default (reducer, actions, defaultValue) => {
const Context = React.createContext();
const Provider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, defaultValue);
const boundActions = {};
for (let key in actions) {
boundActions[key] = actions[key](dispatch);
}
return (
<Context.Provider value={{ state, ...boundActions }}>
{children}
</Context.Provider>
);
};
return { Context, Provider };
};
and when I want to create context I pass actions ,reducer and default values to createContext file and get Context and Provider from that.like this:
productContext.js
import createDataContext from "./createDataContext";
import {storeProducts, detailProduct} from "../data";
const productReducer = (state, action) => {
switch (action.type) {
case "GET_ITEM":
return {...state, productDetail: action.productDetail};
case "ADD_TOTALS":
return {
...state,
cartSubTotal: action.cartSubTotal,
cartTotal: action.cartTotal,
cartTax: action.cartTax
};
case "ADD_TO_CART":
return {
...state,
products: action.tempProducts,
cart: [...state.cart, action.product]
};
default:
return state;
}
};
const getItem = (id) => {
const product = **products**.find(item => item.id === id);
return product;
}
const handleDetail = dispatch => (id) => {
const productDetail = getItem(id);
dispatch({type: "GET_ITEM", productDetail})
};
const addToCart = dispatch => (id) => {
let tempProducts = [...storeProducts];
const index = tempProducts.indexOf(getItem(id));
const product = tempProducts[index];
product.inCart = true;
product.count = 1;
const price = product.price;
product.total = price;
dispatch({
type: "ADD_TO_CART",
tempProducts,
product
});
const data = addTotals();
dispatch({
type: "ADD_TOTALS",
cartSubTotal: data.cartSubTotal,
cartTotal: data.cartTotal,
cartTax: data.cartTax
});
};
const addTotals = () => {
let subTotal = 0;
**cart**.map(item =>{ (subTotal += item.total)});
const tempTax = subTotal * 0.1;
const tax = parseFloat(tempTax.toFixed(2));
const total = subTotal + tax;
return {cartSubTotal: subTotal, cartTax: tax, cartTotal: total};
};
export const {Provider, Context} = createDataContext(
productReducer,
{
handleDetail,
},
{
products: storeProducts,
productDetail: detailProduct,
cart: [],
modalOpen: false,
modalProduct: detailProduct,
cartSubTotal: 0,
cartTax: 0,
cartTotal: 0
);
I can not access cart and products that are bold.how can I use them?
It looks like you're doing a lot of work in the action creator function that would make more sense as part of the reducer. For example, instead of this:
const productReducer = (state, action) => {
switch (action.type) {
case 'GET_ITEM':
return { ...state, productDetail: action.productDetail };
default:
return state;
}
};
const getItem = (id) => {
// no access to state!
const product = products.find((item) => item.id === id);
return product;
};
const handleDetail = (dispatch) => (id) => {
const productDetail = getItem(id);
dispatch({ type: 'GET_ITEM', productDetail });
};
You can do this:
// action value
{ type: 'GET_ITEM', id: 1234 }
// reducer
const productReducer = (state, action) => {
switch (action.type) {
case 'GET_ITEM':
const productDetail = state.products.find(
(item) => item.id === action.id
);
return { ...state, productDetail };
default:
return state;
}
};
Inside the reducer is where you have access to both the action and the state. Try to design your actions so that they contain the smallest amount of information possible to achieve your intention.
Wrote a React Hooks component that renders randomly selected quotes from Redux.
I'm struggling to read the quote from props in the first render (had to write a switch statement in JSX to read from the local React component state on first render instead).
All fine to read from props when one clicks on the button to get a new random quote (i.e. much after the first render). Guess props is too slow to update.
Pointers on how to deal with it most welcome. What I've done is more of a hack as I'm not reading from the Redux store at all on first render.
const defaultState = {};
const NEW_QUOTE = "NEW_QUOTE"
const newQuoteActionCreator = (quoteObject) => {
return {
type: NEW_QUOTE,
payload: quoteObject
};
};
const getNextQuoteReducer = (state = defaultState, action) => {
switch (action.type) {
case NEW_QUOTE:
return {
...state,
data: action.payload
};
default:
return state;
}
};
const store = Redux.createStore(getNextQuoteReducer);
const quotes = [
{
quoteText:"\"AAAAAA.\"",
quoteAuthor:"BBBB",
},
{
quoteText:"\"CCCCC.\"",
quoteAuthor:"DDDD",
}
];
React bit:
const QuoteBox = ({ text, author }) => { //destructuring
return (
<React.Fragment>
<div className="quotable-square">
<div className="content">
<div id="text">{text}</div>
<div id="author" className="author">{author}</div>
</div>
</div>
</React.Fragment>
)
}
const Button = ({ onClick, title }) => {
return (
<button className="new-quote" onClick={onClick}>{title}</button>
)
}
const App = (props) => {
const [quote, setQuote] = React.useState(() => {
const initialQuote = quotes[Math.floor(Math.random() * quotes.length)];
return {
data: initialQuote
}
});
const chosenRandomQuoteToState = () => {
let chosenQuote = randomQuoteFunction(quotes);
props.selectNewQuote(chosenQuote);
}
return (
<React.Fragment>
<div className="container">
<div id="quote-box">
<QuoteBox text={(() => {
switch (typeof props.currentQuote.data) {
case "undefined": return quote.data.quoteText
default: return props.currentQuote.data.quoteText;
}
})()}/>
<div className="actions">
<Button id="new-quote" title="Get New Quote" onClick={chosenRandomQuoteToState} />
</div>
</div>
</div>
</React.Fragment>
)
}
React Redux bit:
const Provider = ReactRedux.Provider;
const mapStateToProps = (state) => {
return {
currentQuote: state
}
const mapDispatchToProps = (dispatch) => {
return {
selectNewQuote: function(quoteToBeNewQuote) {
dispatch(newQuoteActionCreator(quoteToBeNewQuote));
}
}
}
const connect = ReactRedux.connect
const Container = connect(mapStateToProps, mapDispatchToProps)(App);
const AppWrapper = () => {
return (
<Provider store= {store}>
<Container />
</Provider>
);
};
ReactDOM.render(<AppWrapper />, document.getElementById('app'));
Newbie, so be easy on me :)
Try something like this:
const initialState = {
quotes: [
{
quoteText: '"AAAAAA."',
quoteAuthor: 'BBBB',
},
{
quoteText: '"CCCCC."',
quoteAuthor: 'DDDD',
},
],
}; //renamed defaultState
const mapStateToProps = state => {
//state should hold data, container (map state) should
// create values based on that data
const { quotes } = state;
return {
currentQuote:
quotes[Math.floor(Math.random() * quotes.length)],
};
};
const mapDispatchToProps = dispatch => ({
selectNewQuote: dispatch(newQuoteActionCreator()),
});
const getNextQuoteReducer = (
state = defaultState,
action
) => {
switch (action.type) {
case NEW_QUOTE:
return {
...state,
quotes: [...state.quotes], //forces app container to re run
};
default:
return state;
}
};
And get rid of useState in your components as well as all complex logic with your action, the action only needs a type and your reducer will re refernce quites so App container will re run and pick another random quote.
Rather than messing with extra state in your component, you should go all-in with letting redux manage the state of the current quote. Since you want a random quote selected in your store at the very start, you could pre-select a random quote selection in your store's initial state or dispatch the selection of a random quote as an effect so it happens on mount. Here's the latter approach:
const App = (props) => {
useEffect(() => {
chosenRandomQuoteToState();
}, []);
const chosenRandomQuoteToState = () => {
let chosenQuote = randomQuoteFunction(quotes);
props.selectNewQuote(chosenQuote);
};
const currentQuote = props.currentQuote.data;
return (
<React.Fragment>
<div className="container">
<div id="quote-box">
{currentQuote && <QuoteBox text={currentQuote}/>}
<div className="actions">
<Button id="new-quote" title="Get New Quote" onClick={chosenRandomQuoteToState} />
</div>
</div>
</div>
</React.Fragment>
)
}
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.