Use same instance of socket across React application - javascript

The goal is to initiate a socket connection in App.js on app start and then make this exact socket instance available to other components that are loaded with Router. Online research suggests passing socket to a nested component as follows:
const socket = io();
...
render() {
return(<div className="App"><NestedComponent socket={socket} /></div>)
}
This does not work if socket is passed via Router
<Route path='/somepath' socket={socket} component={SomeComponent}/>
If I attempt using socket in SomeComponent (e.g.: this.props.socket.emit('hi', {})), this breaks app as socket turns out to be undefined in SomeComponent.
I was not able to look up a working solution to either pass same instance of a socket with a Router, or use Redux to make socket part of the app state and provide it to lower level components.
Any input on this is much appreciated.

You can use render method and pass the socket like this:
<Route path='/somepath'
render={(props) => (<SomeComponent socket={socket} {...props} />)} />
Notice: {...props} is being passed to access router props like location, history, match etc.
In SomeComponent:
// to access socket props
this.props.socket
So, this should work fine now:
this.props.socket.emit('hi', {})

Related

Should I use connect or hooks for react redux and which has better performance?

I am using react-redux for my react project, apparently, there are 2 ways to use the redux state
connect or useSelector,
My redux store has a reducer for each page,
For Home Page > homePageReducer
For Message Page > messagePageReducer
For Authentication > authReducer
For User's blog > blogReducer
For Settings > userSettingsReducer
For User's Profile > userProfileReducer
In my top-level component or the main component, I have used a selector hook to get all the reducer and passed down the reducers as props to the required components
const {home, messages, auth, settings, blogs} = useSelector( (state:RootState) => state)
return(
<main>
<Switch>
<Route exact to={HOME_ROUTE}>
<HomeApp auth={auth} settings={settings} userProfile={userProfile}/>
</Route>
<Route exact to={CHAT_ROUTE}>
<ChatApp auth={auth} messages={messages} userProfile={userProfile}/>
</Route>
<Route exact to={BLOG_ROUTE}>
<BlogApp auth={auth} blogs={blogs} userProfile={userProfile}/>
</Route>
</Switch>
</main>
)
Is it a good architecture for my project and bring no performance issue for my project or should I use connect or useSelector hook inside those components?
What is better?
Redux has a very useful Style Guide explaining all of the current best practices. There are a few recommendations on that list that are applicable to your example.
Use the React-Redux Hooks API
Prefer using the React-Redux hooks API (useSelector and useDispatch) as the default way to interact with a Redux store from your React components.
Connect More Components to Read Data from the Store
Prefer having more UI components subscribed to the Redux store and reading data at a more granular level. This typically leads to better UI performance, as fewer components will need to render when a given piece of state changes.
Call useSelector Multiple Times in Function Components
When retrieving data using the useSelector hook, prefer calling useSelector many times and retrieving smaller amounts of data, instead of having a single larger useSelector call that returns multiple results in an object.
You definitely want to use useSelector. Rather than selecting everything in the parent and passing it down, your Route render components should take no props and get everything that they need from Redux themselves.
const App = {
return(
<Switch>
<Route exact to={HOME_ROUTE}>
<HomeApp />
</Route>
<Route exact to={CHAT_ROUTE}>
<ChatApp />
</Route>
<Route exact to={BLOG_ROUTE}>
<BlogApp />
</Route>
</Switch>
)
}
const HomeApp = () => {
const userProfile = useSelector( (state: RootState) => state.user );
// We probably don't need the whole auth object
const isLoggedIn = useSelector( (state: RootState) => !! state.auth );
// Do you need to return *every* setting? Just select what you need.
const settings = useSelector( (state: RootState) => state.settings );
...
}
You might want to create selector functions especially for commonly-accessed values like userProfile.
An alternative to modifying the current components HomeApp etc. would be to create a HomeScreen component as a wrapper around HomeApp and keep HomeApp as a pure presentational component. HomeScreen would get all of the data from Redux and call HomeApp with the correct props.

React redux - dispatch in App.js outside provider?

Let me explain my problem. I have an app reacting on an RBAC model using Redux. To do this, I need to call my dispatch() in the useEffect when loading my app to check if the user is authenticated or not. If yes, I would need to dispatch its information contained in the jwt token.
So I did something like this (in App.jsx) :
const App = () => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const dispatch = useDispatch()
const authentication = () =>
isAuthenticated ? (
<Redirect to="/app" />
) : (
<PublicRoutes />
);
useEffect(() => {
setIsAuthenticated(Auth.isAuth())
if(isAuthenticated){
store.dispatch(setConnectedUser({name:"Jude"}))
}
}, [isAuthenticated])
const store = createStore(rootReducer, composeWithDevTools(
applyMiddleware(thunk),
));
return (
<Provider store={store}>
<HashRouter>
<Switch>
<Route path="/app" component={PrivateRoutes} />
<Route path="" render={authentication} />
</Switch>
</HashRouter>
</Provider>
);
}
export default App;
ReactDOM.render(<App />, document.getElementById("root"))
Auth.isAuth() just fetches the token, checks if the token is still valid, and if so it returns true.
But here's the thing, by doing it this way I'm making an error :
Uncaught Error: could not find react-redux context value; please ensure the component is wrapped in a
I understand that the mistake comes from the fact that I want to dispatch() outside the provider, but is it possible to do so? or would I do it wrong? This is my first project with Redux, maybe I didn't fully understand how it works?
Knowing that the dispatch() works very well, it was called at login time before, but I have an error on my header that will retrieve the info from the reducer, it tries to display the store information before it is there, that's why I would like to do the control at application loading, and not only at login.
With your code you aren't able to use redux in this component, just in all his children.
You can just set the provider outside, maybe in the index.js (or wherever you do the ReactDOM.render() or any superior call).
Or if you wish, you can create any new element that will be used in App.js, like 'router.js' or similar where you can check your logic and redirect for where you want.

How to get params from url with React front end and express backend?

I'm building an react app with an express backend. I'm now adding socket io chat functionality to the front end. Everything works as expected but now I want to access params from the url to set the socket-io channel name.
I want the user to visit localhost:3000/foo and the react frontend to be able to access the foo parameter.
What's the best way to do this?
At the moment I am serving the static files like so:
app.use(express.static(`${__dirname}/../client/dist`));
I tried to add react-router-dom with the following code but it doesnt display the page at all:
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route } from 'react-router-dom'
import App from './components/App.jsx';
ReactDOM.render(
<Router>
<Route path="/:id" component={App} />
</Router>,
document.querySelector('#app'));
Whenever I add something to the end of the url (I fi type in something other than ‘/‘ the page does not display. I get the error “cannot GET /foo”
I've also tried this but then the front end doesn't display either:
app.get('/:id', (req, res) => {
console.log('-----------', req.params.id)
})
My ultimate goal would be to only display the chat app when a user visits localhost:3000/chat/:channelId
If you are using react-router your approach is perfectly fine, you should be able to retrieve channel name from match parameter
const App = ({ match }) => (
<div>
<h2>You are listening to: {match.params.id}</h2>
</div>
)
Look at https://reacttraining.com/react-router/web/example/ambiguous-matches
By referring the answer provided by #mavarazy. You could write it in a class and get the parameter via props.
export default class User extends Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
<h2>You are listening to: {this.props.match.params.id}</h2>
</div>
)
}

React Router Route Params in Redux

Using redux and react router, I would like to access a route parameter on a component other than the one specified by the route.
I've got react-router, redux, and react-router-redux set up as follows and I would like to access the reportId parameter from my redux store.
const history = syncHistoryWithStore(browserHistory, store);
const store = createStore(reducers);
const routes = (
<Router history={history}>
<Route path="/" component={MainLayout} >
<IndexRoute component={Home} />
<Route path="reports">
<Route path=":reportId" component={ReportViewerContainer} />
</Route>
</Route>
</Router>
);
ReactDOM.render(
<Provider store={store}>{router}</Provider>,
document.getElementById('root')
);
I've tried hooking up react-redux-router to store the route state, but I've found that there is nothing useful stored inside of redux besides the full path to the active route. This has left me with the options of either parsing out the reportId from the path in redux, or creating my own custom action in redux and updating it with the componentWillReceiveProps lifecycle method inside of my ReportViewerContainer component. There must be a better way?
If you want to access the router parameter inside the component, the best way to achieve this by using withRouter
https://egghead.io/lessons/javascript-redux-using-withrouter-to-inject-the-params-into-connected-components
You will find the better understanding with above example.
If you want to access the router in the redux store, there's a HOC called withRouter (docs). If I remember correctly, this will pass any router state to your component as props.
The only change you need to make to your component would be this:
import { withRouter } from 'react-router'
export default withRouter(ReportViewerContainer);
So I think you can use context to fetch the location that will be passed through the Router. ( Refer that link on how it works.) This should give you a solid direction to work on.
This can also help you down the lane to work with internationalization as well.
class MyComponent extends React.Component {
static contextTypes = {
router: React.PropTypes.object
};
render(){
// By declaring context type here, and childContextTypes
// on the parent along with a function with how to get it,
// React will traverse up and look for the `router` context.
// It will then inject it into `this.context`, making it
// available here.
}
}

Passing props from react router to children on the server

I'm building an Isomorphic app using React, react-router v3 and material-ui. One of the requirements of material-ui in server-side rendering is to pass to the theme the client's user agent, so MUI will be able to prefix their inline styles accordingly.
Originally the root component of the app was the router, i.e on the server side:
<RouterContext {...renderProps}/>
And on the client:
<Router children={routes} history={browserHistory} />
Now, since I didn't know how to pass the user agent to the RouterContext on the server, I came up with an (ugly?) solution: I've created a useless component named Root, to whom I passed the user agent, and Root had the router as his children, i.e. on the server side:
<Root userAgent={userAgent}>
<RouterContext {...renderProps}/>
</Root>
and on the client:
<Root>
<Router children={routes} history={browserHistory} />
</Root>
Now, everything works well but I don't really like creating that useless element if I don't have to, so my question is - is there a better way?
Can I somehow pass the user agent to RouterContext on the server, which will in turn pass it to the Index Route which will create the theme?
Here's the code of Root if someone's interested:
class Root extends React.Component {
constructor(props){
super();
this.muiTheme = getMuiTheme(customTheme, { userAgent: props.userAgent });
}
render () {
return (
<MuiThemeProvider muiTheme={this.muiTheme}>
{this.props.children}
</MuiThemeProvider>
);
}
}
You can use createElement on RouterContext to achieve this.
<RouterContext
{...renderProps}
createElement={(Component, props) => <Component {...props} userAgent={data.userAgent} />}
/>
This will make userAgent available in the props of all route components.

Categories

Resources