0

I'm trying use context api with react-router. I have a container which has a list of songs in its trackList prop from mapStateToProps and I want to pass that to a stateless component called TrackDetail using context api (no react-redux). As you'll see below I've used both the useContext hook and the render props pattern without sucess to get that value from context.

This is what I have in the Routes file:

import React, { Component } from "react";
import { BrowserRouter as Router, Route, Switch, Redirect } from "react-router-dom";
import TrackList from "../../containers/TrackList/TrackList";
import TrackDetail from "../TrackDetail/TrackDetail";

class Routes extends Component {
  render() {
    return (
      <div className="Routes">
        <Router>
          <Switch>
            {this.props.children}
            <Route path="/tracks" component={TrackList} exact />
            <Route path="/tracks/:id" component={TrackDetail} exact/>
            <Route exact path="/" render={() => (<Redirect to="/tracks" />)} />
            <Route path="*" render={() => (<Redirect to="/tracks" />)} />
          </Switch>
        </Router>
      </div>
    );
  }
}

export default Routes;

This is my TrackList.jsx container (connected to redux):

import React, { Component } from "react";
import { connect } from "react-redux";
import "./TrackList.scss";
import Spinner from "../../components/shared/Spinner/Spinner";

export const TrackListContext = React.createContext();

class TrackListProvider extends Component {

  state = {
    foo: "bar"
  }

  render() {
    return (
      <TrackListContext.Provider value={this.state}>
        {this.props.children}
      </TrackListContext.Provider>
    )
  }
};

export class UnConnectedTrackList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      value: ""
    };
  }

  componentDidMount() {
    if (this.props.trackList.length === 0) {
      this.props.getTracks()
      .then(()=>this.props.changeSpinnerState());
      
    }
  }

  render() {
    if (this.props.spinnerState) return <Spinner />;
    return <TrackListProvider trackList={this.props.trackList}>
    <div>
    more content
    </div>
    </TrackListProvider>;
    
  }
}

const mapStateToProps = state => {
  const trackList = state.trackList.length !== 0 ? state.trackList : [];
  return {
    trackList,
  };
};
export default connect(
  mapStateToProps,
  null
)(UnConnectedTrackList);

And this is my TrackDetail.jsx:

import React, { useContext } from "react";
import {TrackListContext} from '../../containers/TrackList/TrackList';
import "./TrackDetail.scss";

const TrackDetailCmp = (props) => {
  const value = useContext(TrackListContext);
  // value is undefined
  const currentTrackIndex = 0;
  const { trackList} = props;
  // trackList is undefined

  return (
    <div className="container">
      <h3></h3>
    </div>
  );
};

const withContext = (Component) => {
  return (props) => <TrackListContext.Consumer>
  {(value) => <Component {...props} trackList={value} />}
  </TrackListContext.Consumer>
};

const TrackDetail = withContext(TrackDetailCmp);
export default TrackDetail;

But I'm getting undefined.

Maybe is an issue with the versions.

This is my package.json:

{
  
  "dependencies": {
    "react": "^16.9.0",
    "react-dom": "^16.9.0",
    "react-redux": "^7.1.0",
    "react-router": "^5.0.1",
    "react-router-dom": "^5.0.0",
    "redux": "^4.0.4",
    "redux-thunk": "^2.3.0"
  },
  "devDependencies": {
    "@types/jest": "^24.0.17",
    "@types/react": "^16.9.2",
    "enzyme": "^3.10.0",
    "enzyme-adapter-react-16": "^1.14.0",
    "node-sass": "^4.12.0"
  }
}

1 Answer 1

2

First thing to mention is that react router shouldn't affect anything here as it just decides what components render.

Next for context to work the context consumer must be a child of the context provider. As I can see here the context provider is the "TrackList" component and the consumer is the "Track" component however these are sibling elements (And as you are using switch with react router only one route will ever render so you can't even have them rendered as siblings).

I would try wrapping the whole router in the context provider and as I can't see any usage other then providing the context of the react list I would remove that. You would end up with something a bit like this:

class Routes extends Component {
  render() {
    return (
      <TrackListProvider value={{trackList: this.props.trackList}}>
          <div className="Routes">
            <Router>
              <Switch>
                {this.props.children}
                <Route path="/tracks/:id" component={TrackDetail} exact/>
                <Route exact path="/" render={() => (<Redirect to="/tracks" />)} />
                <Route path="*" render={() => (<Redirect to="/tracks" />)} />
              </Switch>
            </Router>
          </div>
        </TrackListProvider>
    );
  }
}

Then in your track list component I would remove the HOC (you don't wanna bother doing 2 things the same). Finally the result given by the context is the value, by this I mean you set the value of the provider to be value={{trackList: this.props.trackList}} hence the consumer will receive that full object. The result should look like this:

function TrackDetail(props) {
  const { trackList } = useContext(TrackListContext);

  return (
    <div className="container">
      <h3>{trackList}</h3>
    </div>
  );
};

export default TrackDetail;
Sign up to request clarification or add additional context in comments.

3 Comments

<TrackListProvider value={{trackList: this.props.trackList}}> doesn't have the trackList at the beginning. It should be wrapped inside the redux or have the state itself and use that context at those siblings. However, I saw in some threads they don't recommend 1 major context though.
I see your point I'll go with react-redux and use context only on nested childs
Yeah I didn't know where the track list comes from and got no redux experience so just kinda left it open haha. I haven't heard about anything against a major context I only use context for my user authState so I have it at the highest level, but it makes sense to have it at the lowest level possible

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.