Coder Social home page Coder Social logo

Comments (6)

redgoalsuk avatar redgoalsuk commented on June 10, 2024 1

nice that worked thanks! but just one small bug, when im on Link5.js and I press right to goto Link6.js then I press down to goto the content on Link6.js then press up to go back to the menu it jumps to Link5.js instead of setting focus to Link6, its like when going from Link5 to Link6 it does not set focus to Link6 like it ignored that action?

    return (<View style={[styles.menu2, this.props.hasFocusedChild ? styles.menuFocused2 : null]}>
      <TopNavMenuItemFocusable focusKey={'TOPMENU-1'} itemText='Link1' linkTo='' itemId="Link1"  onBecameFocused={() => this.props.history.push("/") } />
      <TopNavMenuItemFocusable focusKey={'TOPMENU-2'} itemText='Link2' linkTo='Link2' itemId="Link2" onBecameFocused={() => this.props.history.push("/Link2") } />
      <TopNavMenuItemFocusable focusKey={'TOPMENU-3'} itemText='Link3' linkTo='Link3' itemId="Link3" onBecameFocused={() => this.props.history.push("/Link3") } />
      <TopNavMenuItemFocusable focusKey={'TOPMENU-4'} itemText='Link4' linkTo='Link4' itemId="Link4" onBecameFocused={() => this.props.history.push("/Link4") } />
      <TopNavMenuItemFocusable focusKey={'TOPMENU-5'} itemText='Link5' linkTo='Link5' itemId="Link5" onBecameFocused={() => this.props.history.push("/Link5") } />
      <TopNavMenuItemFocusable focusKey={'TOPMENU-6'} itemText='Link6' linkTo='Link6' itemId="Link6" onBecameFocused={() => this.props.history.push("/Link6") } />
    </View>);

from react-spatial-navigation.

asgvard avatar asgvard commented on June 10, 2024

Hi,

From the first look I can see that you call initNavigation from many components, while this should be done once at the root of the app.
Please try to change this and see if it helps.
Also make sure that initial “setFocus” is called when the components are mounted.

from react-spatial-navigation.

redgoalsuk avatar redgoalsuk commented on June 10, 2024

Hi

Brilliant thanks! Yes that was the error I think along with having focusable={false} on Link6.js, im posting the working code below if anyone needs it.

I have a followup question regarding focus on items and how to execute actions when focus is obtained on the TouchableOpacity, im currently doing this below but it never executes when the TouchableOpacity item has focus

  render() {
    // console.log('Menu item rendered: ', this.props.realFocusKey);

    return (<TouchableOpacity style={[styles.topNavmenuItem, this.props.focused ? styles.TopNavfocused : null]} onFocus={() => console.log("hasfocus")} ><Link id={this.props.itemId} to={this.props.linkTo} >{this.props.itemText}</Link></TouchableOpacity>);
  }

Is there someway of doing this? My aim is to use the TopNav.js menu items to redirect to different pages when each item has focus rather then keypress to achieve the same thing?

Working example of the issue reported in the original post:

App.js


    import React, { Component, useState } from 'react';
    import './App.css';
    import PrivateRoute from './PrivateRoute';
    import { HashRouter as Router, Route, Link, Switch } from 'react-router-dom';
    import TopNav from './components/TopNav';
    import Link1 from './pages/Link1';
    import Link2 from './pages/Link2';
    import Link3 from './pages/Link3';
    import Link4 from './pages/Link4';
    import Link5 from './pages/Link5';
    import Link6 from './pages/Link6';

    import {initNavigation, setKeyMap, withFocusable} from '@noriginmedia/react-spatial-navigation';

    initNavigation({
      debug: false,
      visualDebug: false
    })

    function App(props) {

      const Auth = JSON.parse(localStorage.getItem('authData'));

      const isAuthenticated = (Auth && Auth.sessionId != '') ? true : true;

        return (

          <Router >

            <div className="App">

            {isAuthenticated && (
              <header className="App-header">
                <TopNav />
              </header>
            )}

            <div>

              <Switch>

              <Route path="/" exact component={Link1} />
              <Route path="/Link2" component={Link2} />
              <Route path="/Link3" component={Link3} />
              <Route path="/Link4" component={Link4} />
              <Route path="/Link5" component={Link5} />
              <Route path="/Link6" component={Link6} />
              </Switch>
            </div>
            </div>
          </Router>
        );
    }

    export default App;

TopNav.js

import React from 'react'
import "core-js/stable";
import "regenerator-runtime/runtime";
import PropTypes from 'prop-types';
import {View, Text, StyleSheet, TouchableOpacity, ScrollView} from 'react-native';
import {initNavigation, setKeyMap, withFocusable} from '@noriginmedia/react-spatial-navigation';

import { Link, withRouter } from 'react-router-dom';

const styles = StyleSheet.create({

  menu2: {
    maxWidth: 900,
    flexDirection: 'row'

  },
  menuFocused2: {
    backgroundColor: '#546e84'
  },
  topNavmenuItem: {

    alignItems: 'center',
    padding: 14,
    //textDecoration: 'none',
    backgroundColor: '#333',
    flexDirection: 'row'
  },

  TopNavfocused: {
    backgroundColor: '#ccc'
  }
});


const KEY_ENTER = 'enter';

const RETURN_KEY = 8;

class TopMenuItem extends React.Component {

  constructor(props) {
    super(props);

  }


  componentDidMount() {


  }

  render() {
    // console.log('Menu item rendered: ', this.props.realFocusKey);

    return (<TouchableOpacity style={[styles.topNavmenuItem, this.props.focused ? styles.TopNavfocused : null]}><Link id={this.props.itemId} to={this.props.linkTo} >{this.props.itemText}</Link></TouchableOpacity>);
  }
}

TopMenuItem.propTypes = {
  focused: PropTypes.bool.isRequired,

  // realFocusKey: PropTypes.string.isRequired
};


const TopNavMenuItemFocusable = withFocusable()(TopMenuItem);



class TopNavMenu extends React.PureComponent {
  constructor(props) {
    super(props);

    this.onPressKey = this.onPressKey.bind(this);
  }


  componentDidMount() {

    document.getElementById('Link1').addEventListener('focus', this.handleFocus);

  }

  componentWillUnmount() {
    window.removeEventListener('keydown', this.onPressKey);
  }

  onPressKey(event) {
    if (event.keyCode === RETURN_KEY) {
      this.props.setFocus();
    }
  }

  render() {
    // console.log('Menu rendered: ', this.props.realFocusKey);

    return (<View style={[styles.menu2, this.props.hasFocusedChild ? styles.menuFocused2 : null]}>
      <TopNavMenuItemFocusable focusKey={'TOPMENU-1'} itemText='Link1' linkTo='' itemId="Link1"  />
      <TopNavMenuItemFocusable focusKey={'TOPMENU-2'} itemText='Link2' linkTo='Link2' itemId="Link2" />
      <TopNavMenuItemFocusable focusKey={'TOPMENU-3'} itemText='Link3' linkTo='Link3' itemId="Link3"  />
      <TopNavMenuItemFocusable focusKey={'TOPMENU-4'} itemText='Link4' linkTo='Link4' itemId="Link4"  />
      <TopNavMenuItemFocusable focusKey={'TOPMENU-5'} itemText='Link5' linkTo='Link5' itemId="Link5"  />
      <TopNavMenuItemFocusable focusKey={'TOPMENU-6'} itemText='Link6' linkTo='Link6' itemId="Link6"  />
    </View>);
  }
}

TopNavMenu.propTypes = {
  hasFocusedChild: PropTypes.bool.isRequired

};

const TopNavMenuFocusable = withFocusable({
  trackChildren: true
})(TopNavMenu);





class TopNavSpatial extends React.PureComponent {
  constructor(props) {
    super(props);

  }


  componentDidMount() {


    this.props.setFocus();
  }


  render() {

    return (<View>
      <TopNavMenuFocusable focusable={true} history={this.props.history} />
    </View>

);
  }
}

TopNavSpatial.propTypes = {
  setFocus: PropTypes.func.isRequired,
  hasFocusedChild: PropTypes.bool.isRequired,
  history: PropTypes.shape({
    push: PropTypes.func.isRequired
  }).isRequired

};


const TopNav = withFocusable({
  trackChildren: true
})(TopNavSpatial);

export default withRouter(TopNav);

Link6.js

/* eslint-disable react/no-multi-comp */
import React from 'react';
import PropTypes from 'prop-types';
import shuffle from 'lodash/shuffle';
import throttle from 'lodash/throttle';
import "core-js/stable";
import "regenerator-runtime/runtime";
import {View, Text, StyleSheet, TouchableOpacity, ScrollView} from 'react-native';

import {setKeyMap, withFocusable} from '@noriginmedia/react-spatial-navigation';


const KEY_ENTER = 'enter';

const styles = StyleSheet.create({
  wrapper: {
    flex: 1,
    maxHeight: 400,
    maxWidth: 800,
    backgroundColor: '#333333',
    flexDirection: 'row'
  },
  content: {
    flex: 1
  },
  menu: {
    maxWidth: 60,
    flex: 1,
    alignItems: 'center',
    justifyContent: 'space-around'
  },
  menuFocused: {
    backgroundColor: '#546e84'
  },
  menuItem: {
    width: 50,
    height: 50,
    backgroundColor: '#f8f258'
  },
  activeWrapper: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center'
  },
  activeProgram: {
    width: 160,
    height: 120
  },
  activeProgramTitle: {
    padding: 20,
    color: 'white'
  },
  programWrapper: {
    padding: 10,
    alignItems: 'center'
  },
  program: {
    height: 100,
    width: 100
  },
  programTitle: {
    color: 'white'
  },
  categoryWrapper: {
    padding: 20
  },
  categoryTitle: {
    color: 'white'
  },
  categoriesWrapper: {
    flex: 1
  },
  focusedBorder: {
    borderWidth: 6,
    borderColor: 'red',
    backgroundColor: 'white'
  }
});

const categories = shuffle([{
  title: 'Featured'
}, {
  title: 'Cool'
}, {
  title: 'Decent'
}]);

const programs = shuffle([{
  title: 'Program 1',
  color: '#337fdd'
}, {
  title: 'Program 2',
  color: '#dd4558'
}, {
  title: 'Program 3',
  color: '#7ddd6a'
}, {
  title: 'Program 4',
  color: '#dddd4d'
}, {
  title: 'Program 5',
  color: '#8299dd'
}, {
  title: 'Program 6',
  color: '#edab83'
}, {
  title: 'Program 7',
  color: '#60ed9e'
}, {
  title: 'Program 8',
  color: '#d15fb6'
}, {
  title: 'Program 9',
  color: '#c0ee33'
}]);

const RETURN_KEY = 8;

/* eslint-disable react/prefer-stateless-function */
class MenuItem extends React.PureComponent {
  render() {
    // console.log('Menu item rendered: ', this.props.realFocusKey);

    return (<TouchableOpacity style={[styles.menuItem, this.props.focused ? styles.focusedBorder : null]} />);
  }
}

MenuItem.propTypes = {
  focused: PropTypes.bool.isRequired

  // realFocusKey: PropTypes.string.isRequired
};

const MenuItemFocusable = withFocusable()(MenuItem);

class Menu extends React.PureComponent {
  constructor(props) {
    super(props);

    this.onPressKey = this.onPressKey.bind(this);
  }

  componentDidMount() {

    window.addEventListener('keydown', this.onPressKey);
  }

  componentWillUnmount() {
    window.removeEventListener('keydown', this.onPressKey);
  }

  onPressKey(event) {
    if (event.keyCode === RETURN_KEY) {
      this.props.setFocus();
    }
  }

  render() {
    // console.log('Menu rendered: ', this.props.realFocusKey);

    return (<View style={[styles.menu, this.props.hasFocusedChild ? styles.menuFocused : null]}>
      <MenuItemFocusable focusKey={'MENU-1'} />
      <MenuItemFocusable focusKey={'MENU-2'} />
      <MenuItemFocusable focusKey={'MENU-3'} />
      <MenuItemFocusable focusKey={'MENU-4'} />
      <MenuItemFocusable focusKey={'MENU-5'} />
      <MenuItemFocusable focusKey={'MENU-6'} />
    </View>);
  }
}

Menu.propTypes = {
  setFocus: PropTypes.func.isRequired,
  hasFocusedChild: PropTypes.bool.isRequired

  // realFocusKey: PropTypes.string.isRequired
};

const MenuFocusable = withFocusable({
  trackChildren: true
})(Menu);

class Content extends React.PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      currentProgram: null
    };

    this.onProgramPress = this.onProgramPress.bind(this);
  }

  onProgramPress(programProps, {pressedKeys} = {}) {
    if (pressedKeys && pressedKeys[KEY_ENTER] > 1) {
      return;
    }
    this.setState({
      currentProgram: programProps
    });
  }

  render() {
    // console.log('content rendered: ', this.props.realFocusKey);

    return (<View style={styles.content}>
      <Active program={this.state.currentProgram} />
      <CategoriesFocusable
        focusKey={'CATEGORIES'}
        onProgramPress={this.onProgramPress}
      />
    </View>);
  }
}

Content.propTypes = {
  // realFocusKey: PropTypes.string.isRequired
};

const ContentFocusable = withFocusable()(Content);

class Active extends React.PureComponent {
  render() {
    const {program} = this.props;

    const style = {
      backgroundColor: program ? program.color : 'grey'
    };

    return (<View style={styles.activeWrapper}>
      <View style={[style, styles.activeProgram]} />
      <Text style={styles.activeProgramTitle}>
        {program ? program.title : 'No Program'}
      </Text>
    </View>);
  }
}

Active.propTypes = {
  program: PropTypes.shape({
    title: PropTypes.string.isRequired,
    color: PropTypes.string.isRequired
  })
};

Active.defaultProps = {
  program: null
};

class Program extends React.PureComponent {
  render() {
    // console.log('Program rendered: ', this.props.realFocusKey);

    const {color, onPress, focused, title} = this.props;

    const style = {
      backgroundColor: color
    };

    return (<TouchableOpacity
      onPress={onPress}
      style={styles.programWrapper}
    >
      <View style={[style, styles.program, focused ? styles.focusedBorder : null]} />
      <Text style={styles.programTitle}>
        {title}
      </Text>
    </TouchableOpacity>);
  }
}

Program.propTypes = {
  title: PropTypes.string.isRequired,
  color: PropTypes.string.isRequired,
  onPress: PropTypes.func.isRequired,
  focused: PropTypes.bool.isRequired

  // realFocusKey: PropTypes.string.isRequired
};

const ProgramFocusable = withFocusable()(Program);

class Category extends React.PureComponent {
  constructor(props) {
    super(props);

    this.scrollRef = null;

    this.onProgramFocused = this.onProgramFocused.bind(this);
    this.onProgramArrowPress = this.onProgramArrowPress.bind(this);
  }

  onProgramFocused({x}) {
    this.scrollRef.scrollTo({
      x
    });
  }

  onProgramArrowPress(direction, {categoryIndex, programIndex}) {
    if (direction === 'right' && programIndex === programs.length - 1 && categoryIndex < categories.length - 1) {
      this.props.setFocus(`CATEGORY-${categoryIndex + 1}`);

      return false;
    }

    return true;
  }

  render() {
    // console.log('Category rendered: ', this.props.realFocusKey);

    return (<View style={styles.categoryWrapper}>
      <Text style={styles.categoryTitle}>
        {this.props.title}
      </Text>
      <ScrollView
        horizontal
        ref={(reference) => {
          if (reference) {
            this.scrollRef = reference;
          }
        }}
      >
        {programs.map((program, index) => ((<ProgramFocusable
          {...program}
          focusKey={`PROGRAM-${this.props.realFocusKey}-${index}`}
          onPress={() => this.props.onProgramPress(program)}
          onEnterPress={this.props.onProgramPress}
          key={program.title}
          onBecameFocused={this.onProgramFocused}
          onArrowPress={this.onProgramArrowPress}
          programIndex={index}
          categoryIndex={this.props.categoryIndex}
        />)))}
      </ScrollView>
    </View>);
  }
}

Category.propTypes = {
  title: PropTypes.string.isRequired,
  onProgramPress: PropTypes.func.isRequired,
  realFocusKey: PropTypes.string.isRequired,
  categoryIndex: PropTypes.number.isRequired,
  setFocus: PropTypes.func.isRequired
};

const CategoryFocusable = withFocusable()(Category);

class Categories extends React.PureComponent {
  constructor(props) {
    super(props);

    this.scrollRef = null;

    this.onCategoryFocused = this.onCategoryFocused.bind(this);
  }

  onCategoryFocused({y}) {
    this.scrollRef.scrollTo({
      y
    });
  }

  render() {
    // console.log('Categories rendered: ', this.props.realFocusKey);

    return (<ScrollView
      ref={(reference) => {
        if (reference) {
          this.scrollRef = reference;
        }
      }}
      style={styles.categoriesWrapper}
    >
      {categories.map((category, index) => (<CategoryFocusable
        focusKey={`CATEGORY-${index}`}
        {...category}
        onProgramPress={this.props.onProgramPress}
        key={category.title}
        onBecameFocused={this.onCategoryFocused}
        categoryIndex={index}

        // preferredChildFocusKey={`PROGRAM-CATEGORY-${index}-${programs.length - 1}`}
      />))}
    </ScrollView>);
  }
}

Categories.propTypes = {
  onProgramPress: PropTypes.func.isRequired,
  realFocusKey: PropTypes.string.isRequired
};

const CategoriesFocusable = withFocusable()(Categories);

class Spatial extends React.PureComponent {
  constructor(props) {
    super(props);

    this.onWheel = this.onWheel.bind(this);
    this.throttledWheelHandler = throttle(this.throttledWheelHandler.bind(this), 500, {trailing: false});
  }

  componentDidMount() {
    window.addEventListener('wheel', this.onWheel, {passive: false});
  }

  componentWillUnmount() {
    window.removeEventListener('wheel', this.onWheel);
  }

  onWheel(event) {
    event.preventDefault();
    this.throttledWheelHandler(event);
  }

  throttledWheelHandler(event) {
    event.preventDefault();
    const {deltaY, deltaX} = event;
    const {navigateByDirection} = this.props;

    if (deltaY > 1) {
      navigateByDirection('down');
    } else if (deltaY < 0) {
      navigateByDirection('up');
    } else if (deltaX > 1) {
      navigateByDirection('right');
    } else if (deltaX < 1) {
      navigateByDirection('left');
    }
  }

  render() {
    return (<View style={styles.wrapper}>
      <MenuFocusable
        focusKey={'MENU'}
      />
      <ContentFocusable
        focusKey={'CONTENT'}
      />
    </View>);
  }
}

Spatial.propTypes = {
  navigateByDirection: PropTypes.func.isRequired
};

const SpatialFocusable = withFocusable()(Spatial);

class Link6Spatial extends React.PureComponent {
  constructor(props) {
    super(props);

  }


  componentDidMount() {

    this.props.setFocus();
  }


  render() {

    return (<View>
      <SpatialFocusable focusable={true} />
    </View>);
  }
}

Link6Spatial.propTypes = {
  setFocus: PropTypes.func.isRequired,
  hasFocusedChild: PropTypes.bool.isRequired

};


const Link6 = withFocusable({
  trackChildren: true
})(Link6Spatial);

export default Link6;

from react-spatial-navigation.

asgvard avatar asgvard commented on June 10, 2024

Glad it worked :)
In case you want to do something when item is focused, you can use onBecameFocused callback prop that is automatically exposed from the component that is wrapped into withFocusable:
https://github.com/NoriginMedia/react-spatial-navigation#using-props-on-focusable-components

So for example in your TopNav component you can listen to onBecameFocusable on each of the menu items and trigger page navigation.

onFocus callback on TouchableOpacity is more like a native browser "onfocus" event, which is not the same as the "internal" focus from this library.

from react-spatial-navigation.

redgoalsuk avatar redgoalsuk commented on June 10, 2024

Im getting the hang of this :) I removed the setFocus inside Link6.js and that now keeps focus on the menu item Link6 when going from the menu to the content and then back again.

from react-spatial-navigation.

asgvard avatar asgvard commented on June 10, 2024

Closing as resolved

from react-spatial-navigation.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.