User Tools

Site Tools


reactjs_and_friends

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Next revision Both sides next revision
reactjs_and_friends [2021/10/03 16:27]
sausage created
reactjs_and_friends [2021/10/04 11:20]
sausage Added in code files.
Line 78: Line 78:
  
 ==== index.js ==== ==== index.js ====
 +
 +<code javascript>​
 +import React from '​react';​
 +import ReactDOM from '​react-dom';​
 +import '​./​index.css';​
 +import App from '​./​App';​
 +import { createStore,​ applyMiddleware } from '​redux';​
 +import thunk from '​redux-thunk';​
 +import { Provider } from '​react-redux';​
 +import reducer from '​./​reducers/​reducer';​
 +
 +const store = createStore(reducer,​ applyMiddleware(thunk));​
 +
 +ReactDOM.render(
 +  <​React.StrictMode>​
 +    <​Provider store={store}>​
 +      <App />
 +    </​Provider>​
 +  </​React.StrictMode>,​
 +  document.getElementById('​root'​)
 +);
 +</​code>​
  
 This is the root of our application. Stores and middleware loading are provided by Redux. A middleware will be used to add the ability to run a function from within an action. Normally an action can only work with a standard object. Using middleware, will allow us asynchronously load in data remotely using ''​fetch''​ and then pass the data into the store. This is the root of our application. Stores and middleware loading are provided by Redux. A middleware will be used to add the ability to run a function from within an action. Normally an action can only work with a standard object. Using middleware, will allow us asynchronously load in data remotely using ''​fetch''​ and then pass the data into the store.
Line 90: Line 112:
  
 ==== actions.js ==== ==== actions.js ====
 +
 +<code javascript>​
 +import local_planets_data from '​../​reducers/​planets.json';​
 +
 +export const populate_store_with_data = (data) => {
 +    return {
 +        type: '​LOAD_PLANETS_INTO_STORE',​
 +        data: data
 +    }
 +}
 +
 +export const set_loading = () => {
 +    return {
 +        type: '​SET_LOADING'​
 +    }
 +}
 +
 +export const set_loaded = () => {
 +    return {
 +        type: '​SET_LOADED'​
 +    }
 +}
 +
 +export const set_selected_planet = (id) => {
 +    return {
 +        type: '​SET_SELECTED_PLANET',​
 +        id: id
 +    }
 +}
 +
 +export const set_a_favourite = (data) => {
 +    return {
 +        type: '​SET_A_FAVOURITE',​
 +        data: data
 +    }
 +}
 +
 +export const fetchRemoteData = (dispatch) => {
 +    return (dispatch) => {
 +        dispatch(set_loading());​
 +        return fetch('​https://​waynejohnson.net/​planets', ​
 +        { 
 +            mode: '​cors', ​
 +            method: '​GET', ​
 +            headers: { 
 +                '​Content-Type':​ '​text/​plain',​
 +                '​Accept':​ '​application/​json'​
 +            } 
 +        })
 +        .then(response => response.json())
 +        .then(json => {
 +            dispatch( populate_store_with_data(json) )
 +        }).then( () => {
 +            dispatch(set_loaded());​
 +        });
 +    }
 +    ​
 +}
 +
 +export const fetchLocalPlanetData = (dispatch) => {
 +    return (dispatch) => {
 +        dispatch(set_loading());​
 +        setTimeout(() => {
 +            dispatch( populate_store_with_data(local_planets_data) );
 +            dispatch(set_loaded());​
 +        }, 2000);
 +    }
 +    ​
 +}
 +</​code>​
  
 There are several actions in the file. There are several actions in the file.
Line 106: Line 198:
  
 ==== reducer.js ==== ==== reducer.js ====
 +
 +<code javascript>​
 +const initialState = {
 +    loading: false,
 +    planets: [],
 +    selectedPlanet:​ null,
 +    favourites: []
 +}
 +
 +const reducer = (state = initialState,​ action) => {
 +    switch (action.type) {
 +        case '​LOAD_PLANETS_INTO_STORE':​
 +            return { ...state, planets: action.data }
 +        case '​SET_LOADING':​
 +            return { ...state, loading: true }
 +        case '​SET_LOADED':​
 +            return { ...state, loading: false }
 +        case '​SET_SELECTED_PLANET':​
 +            return { ...state, selectedPlanet:​ action.id }
 +        case '​SET_A_FAVOURITE':​
 +            if (action.data.checked){ //add a favourite
 +                return { 
 +                    ...state, favourites: [...state.favourites,​ action.data.id] ​
 +                }
 +            } else { //remove a favourite
 +                return {
 +                    ...state, favourites: [ ...state.favourites.filter(ids => ids !== action.data.id) ]
 +                }
 +            }
 +        default:
 +            return state
 +    }
 +}
 +
 +export default reducer;
 +</​code>​
  
 Not much to say on the reducer file for the reducer itself, as it's pretty standard fare: responding to the dispatched action, and changing the data in the store. Not much to say on the reducer file for the reducer itself, as it's pretty standard fare: responding to the dispatched action, and changing the data in the store.
Line 120: Line 248:
  
 ==== App.js ==== ==== App.js ====
 +
 +<code javascript>​
 +import {BrowserRouter as Router, Route, NavLink, Redirect} from '​react-router-dom';​
 +import { useEffect } from '​react';​
 +import { useDispatch } from '​react-redux';​
 +import { fetchRemoteData,​ fetchLocalPlanetData } from '​./​actions/​actions';​
 +import ListContainer from '​./​views/​list-container.js';​
 +import Favourites from '​./​views/​favourites.js';​
 +import LoadIndicator from '​./​components/​loadindicator.js';​
 +import '​./​App.css';​
 +
 +const App = () => {
 +
 +  const dispatch = useDispatch();​
 +
 +  useEffect( () => { 
 +    dispatch(fetchRemoteData())
 +    //​dispatch(fetchLocalPlanetData())
 +  }, [dispatch] );
 +
 +  return (
 +    <div className="​App">​
 +      <​h1>​Planet Viewer</​h1>​
 +      <​Router>​
 +        <div className="​tabs">​
 +          <​LoadIndicator></​LoadIndicator>​
 +          <NavLink to="/​list"​ activeClassName="​active">​List</​NavLink>​
 +          <NavLink to="/​favourites"​ activeClassName="​active">​Favourites</​NavLink>  ​
 +        </​div>​
 +        <Route path="/​list"​ component={ListContainer}></​Route>​
 +        <Route path="/​favourites"​ component={Favourites}></​Route>​
 +        <​Redirect to="/​list"​ />
 +      </​Router>​
 +    </​div>​
 +  );
 +}
 +
 +export default App;
 +</​code>​
  
 This is the main app component itself. It uses two view components: ''​ListContainer''​ and ''​Favourites''​ and these act as two tabbed views to make up a small Single Page Application (SPA). This is the main app component itself. It uses two view components: ''​ListContainer''​ and ''​Favourites''​ and these act as two tabbed views to make up a small Single Page Application (SPA).
Line 137: Line 304:
  
 ==== list-container.js ==== ==== list-container.js ====
 +
 +<code javascript>​
 +import List from '​../​components/​list.js';​
 +import Details from '​../​components/​details.js';​
 +
 +const ListContainer = () => {
 +
 +    return (
 +        <div className="​list-container">​
 +            <​List></​List>​
 +            <​Details></​Details>​
 +        </​div>​
 +    );
 +}
 +
 +export default ListContainer;​
 +</​code>​
  
 This is only a dumb component that groups the List and Details components. I'll dig into those two components first. We'll look at favourites after that. This is only a dumb component that groups the List and Details components. I'll dig into those two components first. We'll look at favourites after that.
Line 142: Line 326:
  
 ==== list.js ==== ==== list.js ====
 +
 +<code javascript>​
 +import { useSelector,​ useDispatch } from '​react-redux';​
 +import { set_selected_planet } from '​../​actions/​actions';​
 +import PlanetItem from '​./​planet-item.js';​
 +
 +const List = () => {
 +
 +    const dispatch = useDispatch();​
 +
 +    const planets = useSelector(state => state.planets);​
 +    const selectedPlanet = useSelector(state => state.selectedPlanet);​
 +
 +    return (
 +        <div className="​list">​
 +            {
 +                planets.map( planet => (
 +                    <​PlanetItem key={planet.id} planet={planet} onClick={ () => 
 +                        dispatch(set_selected_planet(planet.id)) } 
 +                        active={selectedPlanet === planet.id }
 +                        >
 +                    </​PlanetItem>​
 +                ))
 +            }   
 +        </​div>​
 +    );
 +}
 +
 +export default List;
 +</​code>​
  
 This component uses two useSelector hooks. One to connect to the ''​planets''​ array state in the store, and one to connect to the ''​selectedPlanet''​ state. For each planet, a ''​PlanetItem''​ component is rendered. This component uses two useSelector hooks. One to connect to the ''​planets''​ array state in the store, and one to connect to the ''​selectedPlanet''​ state. For each planet, a ''​PlanetItem''​ component is rendered.
Line 149: Line 363:
  
 ==== details.js ==== ==== details.js ====
 +
 +<code javascript>​
 +import { useSelector,​ useDispatch } from '​react-redux';​
 +import { set_a_favourite } from '​../​actions/​actions';​
 +
 +const Details = (props) => {
 +
 +    const dispatch = useDispatch();​
 +
 +    const planets = useSelector(state => state.planets);​
 +    const selectedPlanet = useSelector(state => state.selectedPlanet);​
 +    const favourites = useSelector(state => state.favourites);​
 +
 +    const planet = planets.find(p => p.id === selectedPlanet);​
 +
 +    const isFavourite = (favourites.filter(i=>​i === selectedPlanet).length > 0);
 +    ​
 +    return (
 +        ​
 +            <div className="​details">​
 +                { planet ? 
 +                    <>
 +                        <div>
 +                            <h2>{ planet.name }</​h2>​
 +                            <​p>​Environment:​ <span className="​environment">​{ planet.environment}</​span></​p>​
 +                            <​p>​Resources:</​p>​
 +                            <ul>
 +                                { 
 +                                    planet.resources.map( (resource, index) => (
 +                                        <li key={index} className="​resource">​{ resource }</​li>​
 +                                    )) 
 +                                }
 +                            </ul>
 +                        </​div>​
 +                        <div className="​select-favourite">​
 +                            <div className="​control">​
 +                                <input id="​fav-check"​ type="​checkbox"​ checked={isFavourite} onChange={(control) => {
 +                                    dispatch(set_a_favourite( {id:​selectedPlanet,​ checked: control.currentTarget.checked}))} ​
 +                                }/>
 +                                <label htmlFor="​fav-check">​Favourite</​label>​
 +                            </​div>​
 +                        </​div>​
 +                    </>
 +                : <​span></​span>​
 +                }
 +            </​div>​
 +        ​
 +    );
 +}
 +
 +export default Details;
 +</​code>​
  
 This is the second component in the pair under the list-component view. It's job is to respond to the change in the selectedPlanet state. But to the user, it appears to respond to the selection click from the list. This is the second component in the pair under the list-component view. It's job is to respond to the change in the selectedPlanet state. But to the user, it appears to respond to the selection click from the list.
Line 158: Line 424:
  
 ==== favourites.js ==== ==== favourites.js ====
 +
 +<code javascript>​
 +import { set_selected_planet } from '​../​actions/​actions';​
 +import { useSelector,​ useDispatch } from '​react-redux';​
 +
 +const Favourites = () => {
 +    const dispatch = useDispatch();​
 +
 +    const favourites = useSelector(state => state.favourites);​
 +    const planets = useSelector(state => state.planets);​
 +    const favouritePlanets = planets.filter(p => favourites.includes(p.id))
 +
 +    return (
 +        <div className="​favourites">​
 +            { favouritePlanets.length > 0 ?
 +                favouritePlanets.map( planet => (
 +                    <div key={planet.id} className="​planet-item"​ onClick={ () => 
 +                        dispatch(set_selected_planet(planet.id)) ​
 +                        }>{` \u2605 ${planet.name} `}
 +                    </​div>​
 +                ))
 +                : <​span>​No favourites yet.</​span>​
 +            }   
 +        </​div>​
 +    );
 +}
 +
 +export default Favourites;
 +</​code>​
  
 Finally we come to the ''​favourites''​ view component. It uses two of the pieces of state: the ''​favourites''​ indexes, and the array of ''​planets''​. ​ Finally we come to the ''​favourites''​ view component. It uses two of the pieces of state: the ''​favourites''​ indexes, and the array of ''​planets''​. ​
reactjs_and_friends.txt ยท Last modified: 2021/11/05 01:35 by sausage