React with Redux
Go into your React
project app folder and install following:
npm install --save redux react-redux
We will create app called Songs.
To connect React with Redux, we will create two new components, Provider
and Connect
. Connect
can communicate with Provider
, which is at the top of hierarchy through contacts
system. Contacts
system allow parent component to communicate with any child component, even there is another component between them.
Structure
src/actions/index.js
We called this file index.js because when we then import it, we dont need to enter full path, we can specify only directory, it will automatically look for index.js.
//Action creator
export const selectSong = (song) => {
//Return an action
return {
type: 'SONG_SELECTED',
payload: song
};
};
src/reducers/index.js
import { combineReducers } from 'redux';
const songsReducer = () => {
return [
{
title: 'No Scrubs',
duration: '4:05'
},
{
title: 'Macarena',
duration: '2:30'
},
{
title: 'All Star',
duration: '3:15'
},
{
title: 'I want it that way',
duration: '1:45'
}
];
};
const selectedSongReducer = (selectedSong = null, action) => {
if (action.type === 'SONG_SELECTED') {
return action.payload;
}
return selectedSong;
}
export default combineReducers({
songs: songsReducer,
selectedSong: selectedSongReducer
});
src/components/SongList.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { selectSong } from '../actions';
class SongList extends Component {
renderList() {
return this.props.songs.map((song) => {
return (
<div className="item" key={song.title}>
<div className="right floated content">
<button
className="ui button primary"
onClick="{() => this.props.selectSong(song)}"
>
Select
</button>
</div>
<div className="content">{song.title}</div>
</div>
);
});
}
render() {
return <div className="ui divided list">{this.renderList()}</div>;
}
}
//here we are getting data from redux store and sending it to react component, there will be available as this.props
const mapStateToProps = (state) => {
return {
songs: state.songs
};
}
//we are passing selectSong as second argument
//connect function will take selectSong action creator and pass it to our component as prop
//connect function is calling dispatch behind the scenes, each time state is updated
export default connect(mapStateToProps, {
selectSong: selectSong
})(SongList);
src/components/SongDetail.js
import React from 'react';
import { connect } from 'react-redux';
const SongDetail = (props) => {
if (!props.song) {
return <div>Select a song</div>
}
return (
<div>
{props.song.title}
{props.song.duration}
</div>
);
}
const mapStateToProps = (state) => {
return { song: state.selectedSong }
}
export default connect(mapStateToProps)(SongDetail);
src/components/App.js
import React from 'react';
import SongList from './SongList';
import SongDetail from './SongDetail';
const App = () => {
return (
<div>
<SongList />
</div>
<div>
<SongDetail />
</div>
);
};
export default App;
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import App from './componentes/App';
import reducers from './reducers';
ReactDOM.render(
<Provider store={createStore(reducers)}>
<App />
</Provider>,
document.querySelector('#root'));
Step by Step
We first do action creator.
actions/index.js
export const selectSong = song => {
return {
type: 'SONG_SELECTED',
payload: song
};
};
Then we do reducers. One reducer will return list of all songs. Second reducer will allow to select specified song after user click on the button. At the top we will import redux
library and combine all reducers
together with combineReducers
function.
We will pass to combineReducers
object. Keys of this object will be the keys which show up in inside our state object.
reducers/index.js
import { combineReducers } from 'redux';
const songsReducer = () => {
return [
{
title: 'No Scrubs',
duration: '4:05'
},
{
title: 'Macarena',
duration: '2:30'
},
{
title: 'All Star',
duration: '3:15'
},
{
title: 'I want it that way',
duration: '1:45'
}
];
};
const selectedSongReducer = (selectedSong = null, action) => {
if (action.type == 'SONG_SELECTED') {
return action.payload;
}
return selectedSong;
};
export default combineReducers({
songs: songsReducer,
selectedSong: selectedSongReducer
});
Then we need to import Provider
, createStore
and reducers
. We need also wrap <App/>
with <Provider>
.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import App from './components/App';
import reducers from './reducers';
ReactDOM.render(
<Provider store={createStore(reducers)}>
<App />
</Provider>,
document.query('#root')
);
We now want to get list of songs into our SongList
component. We will use connect
for that.
mapStateToProps
- we will take our state
and map it to props
, so state
will be then available in props
and we can use it SongList
component.
components/SongList.js
import React from 'react';
import { connect } from 'react-redux';
class SongList extends React.Component {
render() {
console.log(this.props); //we now see songs here
return <div>SONGLIST</div>
}
}
const mapStateToProps = (state) => {
console.log(state);
return {
songs: state.songs //songs will be now available in this.props in this component
};
};
export default connect(mapStateToProps)(SongList);
Při práci s reduxem potkáme tři základní konstrukce: store
, akci
a reducer
.
Store
Store je objekt ve kterém jsou uložena naše data. Store poskytuje tyto 3 základní metody, v jednoduchosti je síla.
store.getState() // vrací naše data (state)
store.subscribe(callback) // pokud chceme zjistit že se data změnila
store.dispatch(akce) // provádíme akci, která změní data ve Store uložená
Akce
Pokud chceme změnit data ve store, popíšeme tuto změnu pomocí jednoduchého objektu zvaného akce. Objekt akce má jediný povinný atribut jménem type
, ten slouží pro identifikaci. Další atributy vyplníme libovolně dle potřeby. Jinak řečeno: akce musí být jednoznačně identifikovatelná a musí obsahovat všechna data nutná k jejímu provedení.
{
type: "ADD_ITEM",
text: "Nějaký úkol"
}
Reducer
Posledním dílem skládačky je reducer. Funkce kterou napíšeme a vložíme do store, aby bezpečně modifikovala data podle požadavků vyjádřených akcí. Reducer tedy čeká uvnitř store až zavoláme dispatch(akce) a jakmile se tak stane, store zavolá reducer a předá mu:
- state – současná data aplikace
- action – celý objekt akce tak, jak jsme jej vložili do volání dispatch() (akce obsahuje identifikaci ‘type’ a jakákoliv další data potřebná k provedení)