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>
)
}
Related
The problem is the title.
I am using:
Typescript Node
Typescript React
I am trying to pass a setPage from useState to the subroutine called subRenderSwitch which decides which form to display, while renderSwitch decides which navigation bar to display.
Main App
function App() {
let [page,setPage] = useState("")
let firstRender = useRef(true);
const subRenderSwitch = (str: any) => {
switch (str) {
case 'Login':
return <Landing setPage={setPage} />
case 'Register':
return <Register />
case 'Update Password':
return <UpdatePassword />
default:
return <Landing />
}
}
const renderSwitch = (str: any) => {
switch(str) {
case 'Not Logged':
return {head: <Nav setPage={setPage} />, body: subRenderSwitch(page)}
case 'Logged':
return {head: '', body: ''}
default:
return {head: <Nav setPage={setPage} />, body: subRenderSwitch(page)}
}
}
return (
<div className="App">
{renderSwitch(page).head}
{renderSwitch(page).body}
</div>
);
}
export default App;
export default function Nav ({ setPage }: any) {
let [nav,setNav] = useState('')
const clickHandler = (e: any, str: any) => {
e.preventDefault()
const sendData = async () => {
...
}
sendData().then(res => setNav(res.data.value))
return
}
useEffect(() => {
setPage(nav) // NO ERROR HERE
},[nav,setPage])
const renderSwitch = () => {
switch (nav) {
...
}
}
return (
<div id='navBar'>
{renderSwitch()}
</div>
)
}
export default function Landing ( { setPage }: any ) {
let [message,setMessage] = useState("Enter details to Login")
let [data,setData] = useState({email: '',pass: ''})
let [nav,setNav] = useState('')
let firstRender = useRef(true)
const submitHandler = (e: any) => {
e.preventDefault()
const sendData = async () => {
...
}
sendData().then(res => {
if (res.data != null) {
setMessage("User Found")
setNav('Login')
}
else {
setMessage("User Not Found")
}
})
return
}
useEffect(() => {
if (firstRender.current) {
firstRender.current = false
return
}
setPage(nav) // ERROR CAUSED HERE
},[nav,setPage])
return (
<div id='landingPage'>
...
</div>
)
}
Please know that I am still a newbie and therefore the above may not follow any best practices. Additionally, I will accept any critique that may improve my code.
I can successfully pass setPage to renderSwitch without any trouble, but for subRenderSwitch I get setPage is not a function.
What could be the cause for this error?
I have a strange issue with passing a function as a prop in my React app. Code as follows:
const NewPage = () => {
const [blocks, setBlocks] = useState([]);
const [needsShowImageModal, setNeedsShowImageModal] = useState(false);
const textButtonHandler = () => {
const key = randomInt(0, 1000000000);
const array = blocks.concat({ key, deleteButtonHandler: deleteButtonHandler });
setBlocks(array);
};
function deleteButtonHandler(blockKey) {
// Test functionality, if one text field was added arrray size should
// be 1
console.log(blocks.length);
}
return (
<div>
<ImageModal
show={needsShowImageModal}
onHide={() => setNeedsShowImageModal(false)}
insertButtonHandler={insertImageHandler}
/>
<div className="d-flex">
<NewPageSidebar
textButtonHandler={textButtonHandler}
imageButtonHandler={imageButtonHandler}
spacingButtonHandler={spacingButtonHandler}
/>
<NewPageContent blocks={blocks} />
</div>
</div>
);
};
export default NewPage;
When text button handler is called (a button press) I add a new data model to the blocks array. I have another button handler deleteButtonHandler that is passed to the NewPageContent component (inside the data model). NewPageContent:
const NewPageContent = ({ blocks }) => {
return (
<div className="new-page-content-container border mr-5 ml-5 p-3">
{blocks.map(block =>
<TextBlock
key={block.key}
blockKey={block.key}
deleteButtonHandler={block.deleteButtonHandler}
/>
)}
</div>
);
};
NewPageContent.propTypes = {
blocks: PropTypes.arrayOf(PropTypes.element)
};
export default NewPageContent;
And finally this handler is passed to TextBlock:
const TextBlock = ({ deleteButtonHandler, blockKey }) => {
const [editorState, setEditorState] = useState(
() => EditorState.createEmpty(),
);
const toolbarClickHander = (buttonType, e) => {
e.preventDefault();
switch (buttonType) {
case 'delete':
// Delete button handler called here
deleteButtonHandler(blockKey);
break;
default:
break;
}
};
return(
<div className='text-block'>
<TextBlockToolbar clickHandler={toolbarClickHander} />
<Editor
editorState={editorState}
onChange={setEditorState}
/>
</div>
);
};
If I add one element to blocks via textButtonHandler the component is rendered on screen as expected. However if I tap the delete button and deleteButtonHandler is called it will log the size of the array as 0, Strangely if I add second element and then tap the delete button for that element if logs the array size as 1. It's almost as if it's taking a snapshot of the blocks state just as the new textButtonHandler is assigned to the prop and not using the actual current state of blocks. Any ideas what I might be doing wrong here? Haven't run into this issue before. Thanks
Okay. What is happening here:
You passing a function in an object. That object might have an own context, and you tryes to use that function in that object context, what confuses react. (I know that ECMAScript simple objects has no own context, but react might process that data, so might work in different way .) So, pass each function standalone prop in the child component.
Example: https://codesandbox.io/s/broken-waterfall-vgcyj?file=/src/App.js:0-1491
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [blocks, setBlocks] = useState([
{ key: Math.random(), deleteButtonHandler }
]);
const textButtonHandler = () => {
const key = Math.random();
// const array = blocks.concat({
// key,
// deleteButtonHandler: deleteButtonHandler
// });
setBlocks(prev => prev.concat({ key, deleteButtonHandler }));
};
const deleteButtonHandler = blockKey => {
// Test functionality, if one text field was added arrray size should
// be 1
console.log(blocks.length);
};
return (
<div>
<div className="d-flex">
<NewPageContent
deleteButtonHandler={deleteButtonHandler}
blocks={blocks}
/>
</div>
<button onClick={textButtonHandler}>Handler</button>
</div>
);
}
const NewPageContent = ({ blocks = [], deleteButtonHandler = () => null }) => {
return (
<div className="new-page-content-container border mr-5 ml-5 p-3">
{blocks.map(block => (
<TextBlock
key={block.key}
blockKey={block.key}
// deleteButtonHandler={block.deleteButtonHandler}
deleteButtonHandler={deleteButtonHandler}
/>
))}
</div>
);
};
const TextBlock = ({ deleteButtonHandler = () => null, blockKey = "" }) => {
return (
<div className="text-block">
{blockKey}
<span onClick={deleteButtonHandler}>X</span>
</div>
);
};
I have consoled out your old solution, to compare it.
I currently have a code that displays Api data on the page. This is a list :
API has this structure:
{"hij":{
"low":[{......},{.....}],
"all":[{....},{......}]}
}
How to implement such buttons on Redux? Can you help me a little bit? I watched a few dozen videos on YouTube, read many articles and no examples of how to make buttons which to take different data from API....
On React I implement it this way(App.js):
class App extends React.Component {
state = {
day: 1,
data: [],
filteredData: [],
search: "",
shift: "low"
};
componentDidMount() {
this.fetchData();
}
fetchData = async () => {
const response = await fetch(`...`);
const data = (await response.json()).body;
this.setState({data, shift: Object.keys(data)[0]}, this.filter);
};
loadDay = day => {
this.setState({ day }, this.fetchData);
};
updateSearch = e => {
this.setState({search: e.target.value});
};
filter = () => {
this.setState(({ search, data, shift }) => {
const s = search.toLowerCase();
return {
filteredData: data[shift].filter(n =>
n.term.toLowerCase().includes(s)
)
};
});
};
onClick = ({target: { dataset: { shift }}}) => {
this.setState(() => ({ shift }), this.filter);
};
render() {
const { search, shift, data, filteredData } = this.state;
return (
<div>
<TableSearch value={search}
onChange={this.updateSearch}
onSearch={this.filter}/>
{days.map((day, i) => (
<button key={day}
onClick={() => this.loadDay(i)}
className={i === this.state.day ? "active" : ""}>{day}</button>
))}
<br />
{Object.keys(data).map(n => (
<button data-shift={n}
onClick={this.onClick}
className={n === shift ? "active" : ""}>{n} shift</button>
))}
<TableData data={filteredData} />
</div>
);
}
}
There are a number of things to change to make this a redux compatible application, the least of your concerns would be buttons. So instead of answering the button part directly, here's an annotated refactor of your application to Redux:
import { createStore, applyMiddleware } from 'redux';
import { connect, Provider } from 'react-redux';
import thunk from 'redux-thunk';
// Take out common functionality into seperate functions, such as
// running a search here.
function searchFilter (search, data) {
return data.filter(n => n.term.toLowerCase().includes(search));
}
// This is your reducer function which updates the global redux state,
// depending on which action you dispatch:
// see https://redux.js.org/basics/reducers
function reducer (state = {}, action) {
state = { ...state }
switch (action.type) {
case 'SET_SEARCH':
state.search = action.search.toLowerCase();
break;
case 'RUN_FILTER':
state.shift = action.shift || state.shift;
state.search = action.search || state.search;
state.filteredData = searchFilter(state.search, state.data[state.shift]);
break;
case 'LOAD_DATA_START':
state.day = action.day;
break;
case 'LOAD_DATA_END':
state.data = action.data;
state.shift = Object.keys(data)[0];
state.filteredData = searchFilter(state.search, state.data[state.shift]);
break;
}
return state;
}
// This is your store object which contains an initial state, and a reducer
// that will be used for dispatched actions.
// see https://redux.js.org/basics/store
//
// Redux-thunk is used as middleware to support async data fetching which you will
// also need to read up on, although you don't really need to know how it
// works at first.
// see https://github.com/reduxjs/redux-thunk
const store = createStore(
reducer,
{
day: 1,
data: [],
filteredData: [],
search: "",
shift: "departure"
},
applyMiddleware(thunk)
);
// This is a "thunk" called fetch data, again you can read more
// about thunks in the redux-thunk docs
// see https://github.com/reduxjs/redux-thunk
function fetchData (day) {
return async (dispatch) => {
dispatch({ type: 'LOAD_DATA_START', day });
const response = await fetch(`https://api.iev.aero/api/flights/${days[this.state.day]}`);
const data = (await response.json()).body;
dispatch({ type: 'LOAD_DATA_END', data });
}
}
const days = ["23-08-2019", "24-08-2019", "25-08-2019"];
// Stripped down component, it does not handle any of its own state
// all state is passed to it through the redux connect HOC.
class Root extends React.Component {
componentDidMount() {
this.props.onFetchData(this.props.day);
}
render() {
const { search, shift, data, filteredData, onFilter, onSetSearch, onFetchData } = this.props;
return (
<div>
<TableSearch value={search}
onChange={(e) => onSetSearch(e.target.value)}
onSearch={() => onFilter()} />
{days.map((day, i) => (
<button key={day}
onClick={() => onFetchData(day)}
className={i === day ? "active" : ""}>{day}</button>
))}
<br />
{Object.keys(data).map(n => (
<button data-shift={n}
onClick={(e) => onFilter({ shift: e.target.dataset.shift })}
className={n === shift ? "active" : ""}>{n} shift</button>
))}
<TableData data={filteredData} />
</div>
);
}
}
// This is the "connected" version of the component, which is
// your component wrapped in a connect HOC. When the reducer function
// is run, the two functions below will be executed and your component
// inside will re render.
//
// You can read more about this one in react-redux
// https://react-redux.js.org/
const ConnectedRoot = connect(
(state) => state,
(dispatch) => ({
onFilter: (args) => dispatch({ type: 'RUN_FILTER', ...args }),
onSetSearch: (search) => dispatch({ type: 'SET_SEARCH', search }),
onFetchData: (day) => dispatch(fetchData(day))
})
);
// This is your top level component that you would call in ReactDOM.render
// The Provider component is part of react-redux, and you can read about it
// there, but in most cases it is sufficient to drop it at the very top level
// of your application.
// https://react-redux.js.org/
const App = () => (
<Provider store={store}>
<ConnectedRoot />
</Provider>
);
Before I was doing this with local state but now I need to pass it to Redux. I am not using Redux Forms and I am not going to.
This is how I was/am doing it with local state using the useState hook:
const DynamicInputExample = () => {
const [addShareStructureInput, setAddShareStructureInput] = useState({
inputs: ['input-0'],
});
const appendInput = () => {
const newInput = `input-${addShareStructureInput.inputs.length}`;
setAddShareStructureInput(prevState => ({ inputs: prevState.inputs.concat([newInput]) }));
};
return(
<div id="dynamicInput">
// HERE I MAP THE INPUTS
{addShareStructureInput.inputs.map(input => (
<FormField
key={input}
onChange={() => onChange()}
/>
))}
<div>
<Button
type="button"
// HERE I CALL THE FUNCTION TO ADD NEW INPUT
onClick={() => appendInput()}
>
+ Add More
</Button>
</div>
</div>
)
}
But now I need to remove that hook on that code and make the same logic on Redux.
This is what I've done so far:
Action:
export const shareStructureInputsAction = shareStructureInputs => ({
type: ActionTypes.SHARE_STRUCTURE_INPUTS,
payload: { shareStructureInputs },
});
Reducer:
const initialState = {
shareStructureInputs: ['input-0'],
}
const handlers = {
[ActionTypes.SHARE_STRUCTURE_INPUTS](state, action) {
return {
...state,
// BELOW I NEED TO ADD THE LOGIC TO KEEP THE STATE OF THE INPUTS ADDED
shareStructureInputs: {
...action.payload.shareStructureInputs,
},
};
},
}
So, how can I simulate the same logic/behavior with Redux instead?
It's possible to do it, using something like this:
const initialState = {
shareStructureInputs: ['input-0'],
}
const handlers = {
[ActionTypes.SHARE_STRUCTURE_INPUTS](state, action) {
return {
...state,
shareStructureInputs: [...shareStructureInputs, action.payload.shareStructureInputs],
};
},
}
Action:
export const shareStructureInputsAction = shareStructureInputs => ({
type: ActionTypes.SHARE_STRUCTURE_INPUTS,
payload: shareStructureInputs
});
Reducer:
const initialState = {
shareStructureInputs: ['input-0'],
}
const handlers = {
[ActionTypes.SHARE_STRUCTURE_INPUTS](state, action) {
return {
...state,
shareStructureInputs: action.payload
};
},
}
import { connect } from 'react-redux';
import { shareStructureInputsAction } from 'actions/shareStructureInputsAction';
const DynamicInputExample = (props) => {
const { shareStructureInputs, setShareStructureInputs } = props;
const appendInput = () => {
const newInput = `input-${shareStructureInputs.length}`;
setShareStructureInputs(shareStructureInputs.concat(newInput));
};
return(
<div id="dynamicInput">
// HERE I MAP THE INPUTS
{shareStructureInputs.map(input => (
<FormField
key={input}
onChange={() => onChange()}
/>
))}
<div>
<Button
type="button"
// HERE I CALL THE FUNCTION TO ADD NEW INPUT
onClick={() => appendInput()}
>
+ Add More
</Button>
</div>
</div>
)
}
const mapStateToProps((state) => ({
shareStructureInputs: state[ActionTypes.SHARE_STRUCTURE_INPUTS].shareStructureInputs
}));
const mapDispatchToProps({
setShareStructureInputs: shareStructureInputsAction
})
export default connect(mapStateToProps, mapDispatchToProps)(DynamicInputExample);
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);