In the previous tutorial we showed how to build custom Redux like store, which is a great way to understand how Redux work, but if we want a production grade app, we have to do more work on our custom Redux like store, since we cheated a bit.
For example our redux store emits setState
no matter if the properties are actually changed or not which is ok for a demo app but for a big app with a lot of mapStateToProps it will have performance impact, so let’s not re-invent the wheel but use the Redux store instead.
yarn add redux react-redux
Since Redux like implementation that we did in the previous tutorial is very similar to the actual Redux, there are just a few changes that we need to do in order to start using Redux.
./src/components/App/index.js
import React, { Component } from 'react'; import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; import { ApolloProvider } from 'react-apollo'; import { ApolloClient } from 'apollo-client'; import { HttpLink } from 'apollo-link-http'; import { InMemoryCache } from 'apollo-cache-inmemory'; import PageLayout from '../../containers/PageLayout'; import { Provider } from 'react-redux'; import { createStore} from 'redux'; import reducers from '../../reducers'; import fetch from 'unfetch'; const store = createStore(reducers, {}); export default class App extends Component { render() { const GRAPHQL_URL = 'http://localhost:4001/graphql'; const client = new ApolloClient({ link: new HttpLink({ uri: GRAPHQL_URL, fetch: fetch }), cache: new InMemoryCache() }); return ( <Provider store={store}> <ApolloProvider client={client}> <Router> <Switch> <Route exact path="*" component={PageLayout} /> </Switch> </Router> </ApolloProvider> </Provider> ); } }
what we just did:
– we removed our custom store implementation:
– and replaced it with the Redux provider and create store (line 8 and 9)
– we called the Redux createStore (line 15)
– finally we wrapped the app with the new provider passing the store (line 28)
Next step is just cleaning up the code a bit.
– We renamed the index.js reducer to ./src/reducers/useer.js so we could keep each different reducer types in separate files and we added one main reducer ./src/reducers/index.js that will combine all other reducers. (in our case ./src/reducers/useer.js for now)
./src/reducers/user.js
const CHANGE_USERNAME = 'CHANGE_USERNAME'; const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE'; const initialState = { todos: [], userName: "No Name", editMode: false }; const reducer = (state = initialState, action) => { switch (action.type) { case CHANGE_USERNAME: { let newState = {...state}; newState.userName = action.data; return newState; } case TOGGLE_EDIT_MODE: { let newState = {...state}; newState.editMode = !newState.editMode; return newState; } default: return state; } }; export default reducer;
./src/reducers/index.js
import { combineReducers } from 'redux'; import user from './user'; export default combineReducers({ user });
And the best part is that since our Redux like implementation was so similar to the actual Redux implementation, we have to make just two little changes:
– Replace import connect from '../../containers/Provider/connect';
with the Redux connect import { connect } from 'react-redux';
– And since we added combineReducers
we have to add user
property to access the users reducer.
./src/components/About/index.js ./src/components/Greetings/index.js.
import React, { Component } from 'react'; import { connect } from 'react-redux'; const styles = require('./styles.scss'); const CHANGE_USERNAME = 'CHANGE_USERNAME'; const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE'; class Greetings extends Component { constructor(props) { super(props); } doneEditUsername() { let newName = document.querySelector('#inputField').value; this.props.changeUserName(newName); this.props.toggleLogInPopup(); } usernameChanged(el) { let newName = el.target.value; this.props.changeUserName(newName); } onToggleEditMode() { this.props.toggleLogInPopup(); } render() { let element = <h2 onClick={() =>{ this.onToggleEditMode() }}>Hello: {this.props.userName}</h2>; if(this.props.editMode) element = <h2>Type new name:<input type="text" id='inputField' value={this.props.userName} onChange={(el) => { this.usernameChanged(el);}} /><button onClick={() =>{ this.doneEditUsername() }}>done</button></h2> return ( <div> <div className={styles.wrapper}> {element} </div> </div>); } } const mapStateToProps = ( storeState ) => { return { userName: storeState.user.userName, editMode: storeState.user.editMode } } const mapDispatchToProps = dispatch => { return { toggleLogInPopup: () => { dispatch({type: TOGGLE_EDIT_MODE}); }, changeUserName: (userName) => { dispatch({type: CHANGE_USERNAME, data: userName}); } } }; export default connect( mapStateToProps, mapDispatchToProps )(Greetings);
./src/components/About/index.js ./src/components/About/index.js
import React, { Component } from 'react'; import { connect } from 'react-redux'; const CHANGE_USERNAME = 'CHANGE_USERNAME'; class About extends Component { constructor(props) { super(props); this.state = { userName: this.props.userName, }; } handleChange() { const userName = document.querySelector('input[name=username]').value; this.setState( { userName: userName } ); this.props.onEdit(userName); } render() { return ( <div> <p>This is <input type="text" name="username" value={this.state.userName} onChange={() => { this.handleChange()}} /></p> </div> ); } } //export default About; const mapStateToProps = storeState => ({ userName: storeState.user.userName } ); const mapDispatchToProps = dispatch => ({ onEdit: (userName) => dispatch({ type: CHANGE_USERNAME, data: userName }) }); const AboutContainer = connect( mapStateToProps, mapDispatchToProps )(About); export default AboutContainer;
And delete ./src/containers/Provider
folder.
And now all custom Redux like implementation was replaced wit the actual Redux store. Give it a try and everything shoul;d work like before but using the actual Redux store.