Początek opowieści
Zabrałem się za główną funkcję aplikacji. Jestem dopiero na początku, ale i tak uważam, że idzie nieźle. Dzisiaj szybko opowiem co się udało zrobić.
Poprzednie wpisy z serii:
- Wstęp
- Kilka słów o projekcie
- Pierwsza konfiguracja środowiska
- Krótko o początkach z Reactem
- Bardzo brzydkie style
- Uruchomienie Reduxa i Routera
- Jednak Bootstrap
- Poprawki i nowości
- Początek koszmaru – formularze
- Gorzkie żale związane z walidacją
- Pierwsze, nienajlepsze spotkanie z biblioteką redux-form
- Formularze w końcu zadziałały
Dodawanie opowiadań
Brzmi jak coś ważnego i takie jest w rzeczywistości, mimo, że tak naprawdę sama funkcja dodawania niewiele robi. Pokazywałem już w przeszłości, że mam formularz do dodawania nowych opowiadań. Teraz dodałem do niego trochę logiki, m.in. sprawdzam czy pole z tytułem i typem nie jest puste. Oprócz tego to co wpiszemy jest dodawane w Reduxie i mam do tego dostęp.
Dopóki nie będę miał części serwerowej to postanowiłem ID opowiadań „generować” ręcznie wpisując po prostu kolejne liczby. Oczywiście jak tylko dane będą zapisywane na serwerze to sam serwer będzie się zajmował nadawaniem ID. Jednak do testów i dalszych prac taka forma wystarcza, póki co przynajmniej.
Tak wygląda reducer dla story. Widać m.in. linijkę pobierającą najwyższe ID już zapisanych pozycji w celu przypisania nowemu story wartości o jeden większej:
const yourStoryReducer = function(state = initialState, action) { let max_id = state.stories.length > 0 ? Math.max(state.stories.map((story) => {return story.id})) : 0 switch(action.type) { case types.NEW_STORY: action.story.id = max_id + 1 return { ...state, stories: [...state.stories, action.story] } } return state }
Może ktoś się zastanawia co to za trzykropki w kodzie. Jest to dosyć świeża rzecz w JavaScripcie i pozwala np. w prosty sposób przepisać wszystkie wartości z tablicy czy obiektu. Sensowne wytłumaczenie znajdziesz pod TYM linkiem. Ja używam tego, żeby utworzyć nowy obiekt (bo pamiętamy o immutable w reducerach) i przepisać wszystkie wartości stanu, których nie modyfikowałem.
Sam kontener dla formularza nowych opowiadań wygląda tak:
const NewStoryContainer = React.createClass({ render: function() { return ( <NewStory createStory={this.createStory} /> ) }, createStory: function(values) { const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)) store.dispatch(commonActions.blockUi()) return sleep(1000) .then(() => { this.validate(values) store.dispatch(storyActions.addNewStory({ title: values.title, type: values.type, description: values.description })) store.dispatch(commonActions.unblockUi()) browserHistory.push('/') }) .catch(error => { this.setState({error: error}) store.dispatch(commonActions.unblockUi()) return Promise.reject(error) }) }, validate: function(values) { if(!values.title) { throw new SubmissionError({title: "Title can't be empty"}) } if(!values.type) { throw new SubmissionError({type: "Type can't be empty"}) } } }) export default NewStoryContainer
Tak jak wspomniałem jest tutaj bardzo prosta walidacja. Oprócz tego dodałem metodę obsługującą submit formularza i wywołującą akcję Reduxa.
Ponieważ dane są zapisywane tylko w store Reduxa to oczywiście giną po odświeżeniu strony…
Lista opowiadań
Do tej pory na dashboardzie właściwie nic nie było – tylko puste panele. Teraz kiedy można już dodawać wpisy to można również zobaczyć ich listę. Na razie wpisy w żaden sposób nie obsługują użytkowników więc po prostu wyświetlane są wszystkie dostępne :D
Kod dla tej listy to nic szczególnego:
const Yours = React.createClass({ render: function() { const stories = this.props.yourStories.map((story) => { return(<li key={story.id}><Link to={'/story/details/' + story.id}>{story.title}</Link></li>) }) return ( <Panel header="Yours stories" bsStyle="primary"> {this.props.yourStories.length > 0 ? <ul>{stories}</ul> : <span>You haven't any story</span>} </Panel> ) } }) const mapStateToProps = (store) => { return { yourStories: store.yourStories.stories } } export default connect(mapStateToProps)(Yours)
Po prostu mapuję kolejne opowiadania na elementy listy.
Dodatkowo widzisz, że dodaję tutaj link do strony /story/details/ i o tym za chwilę.
Szczegóły opowiadania
W końcu zaczyna się coś dziać, coś można klikać! No dobra, nie ma tutaj za dużo akcji ale już nie kończymy na wpisaniu danych logowania i przejściu do dashboardu.
Jak pokazałem powyżej, na liście opowiadań znajdują się linki do strony ze szczegółami opowiadania o konkretnym ID. Wygląda ona tak:
Na razie wyświetla się tylko tytuł i ewentualnie opis jeśli go uzupełniliśmy przy tworzeniu story.
W tych dwóch panelach będą wyświetlane odpowiednio rozdziały i paragrafy z możliwością sortowania za pomocą drag&drop.
Z kodu to właściwie ciekawe jest tylko pobieranie wpisu o podanym ID:
const StoryIndexContainer = React.createClass({ render: function() { return ( <Details story={this.props.yourStories.find(s => s.id == this.props.params.id)} /> ) } })
Pytanie skąd biorę to ID? Jest ono parametrem w adresie. Obsługuje to Router:
<Router history={browserHistory}> <Route path='/' component={MainLayout}> <IndexRoute component={IndexContainer} /> <Route path="login" component={LoginContainer} /> <Route path="register" component={RegisterContainer} /> <Route path="story"> <Route path="details/:id" component={StoriesIndexContainer} /> <Route path="new-story" component={NewStoryContainer} /> </Route> </Route> </Router>
Konkretnie linijka <Route path=”details/:id” component={StoriesIndexContainer} /> gdzie za pomocą dwukropka i nazwy zaznaczam, że w tym miejscu w adresie oczekuję argumentu.
Redux DevTools
Przy okazji męczenia się z dodawaniem wpisów zainstalowałem sobie w Chrome dodatek, który nazywa się Redux DevTools. Pozwala on śledzić co się w tym naszym Reduxie dzieje. Wyświetla m.in. jakie akcje są przesyłane, jaka jest zawartość store albo co się po danej akcji zmieniło w danych. Wygląda on tak:
Po lewej mam listę akcji, a po prawej zawartość mojego store.
Jedyne co mi się nie podoba w tym dodatku to to, że żeby działał trzeba ingerować w nasz kod. Konkretnie wymaga on dodania przy tworzeniu store takiego brzydkiego parametru:
const store = createStore(reducers, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
Na szczęście jest to jedyna modyfikacja.
Podsumowując
To tyle na dzisiaj. Coś tam się udaje robić. Coraz bardziej zaczyna mi doskwierać brak części serwerowej albo jakiegoś mechanizmu zapisującego zawartości store w pamięci przeglądarki. Widzę też, że robię coraz więcej mocków, które potem trzeba będzie zamienić na obsługę zapytań do serwera.
Jak zwykle zapraszam do śledzenia mnie również na Facebooku.
Aktualny kod znajdziesz na GitHubie.
Leave a Comment