I need to be able to get the current navigation state from my registered screen components. I expected to find a routes object inside the navigation.state object but alas its not there. I have managed to get this working by setting up my root component in the following way, however this seems convoluted and i cant help but think there must be a cleaner way to achieve this.
App.js
import React, {Component} from 'react'
import {Tabs} from './components/Routes'
import {NavigationActions} from 'react-navigation'
export default class App extends React.Component {
state = {
navState: null
}
componentDidMount(){
const initState = Tabs.router.getStateForAction(NavigationActions.init())
this.getState(null, initState);
}
getState = (prevState, newState) => {
let activeIndex = newState.index
let navState = newState.routes[activeIndex]
this.setState({navState})
}
render() {
return (
<Tabs
onNavigationStateChange={this.getState}
screenProps={{navState: this.state.navState}}/>
)
}
}
<NavigationName
onNavigationStateChange={(prevState, newState) => {
this._getCurrentRouteName(newState)
}}
/>
_getCurrentRouteName(navState) {
if (navState.hasOwnProperty('index')) {
this._getCurrentRouteName(navState.routes[navState.index])
} else {
console.log("Current Route Name:", navState.routeName)
this.setState({navState: setCurrentRouteName(navState.routeName)})
}
}
Related
I have a small issue. I'm very very very new to Nextjs and I'm trying to learn by making a app. I have managed to make a Login system using next and I have few issues when securing routes. I have successfully added a cookie after successful login. Now I want to validate the cookie whenever user go to a protected route. I have followed below steps using this tutorial.
Made a Higher order component and checked the cookie validation using it.
Wrap the protected component using it.
Below is my HOD.
import { useRouter } from "next/router";
import Cookies from 'js-cookie';
const withAuth = (WrappedComponent) => {
return (props) => {
if (typeof window !== "undefined") {
const Router = useRouter();
const accessToken = Cookies.get('token');
if (!accessToken) {
Router.replace("/");
return null;
}
return <WrappedComponent {...props} />;
}
return null;
};
};
export default withAuth;
And then I have wrapped my component using above HOD.
import React, { Component } from 'react';
import withAuth from '../utils/withAuth';
class Home extends Component {
render() {
return (
<div>
HOME
</div>
);
}
}
export default withAuth(Home);
ISSUE #1
Above HOD is showing a console warning saying below.
Warning: Expected server HTML to contain a matching in .
div
Is their anyway I can fix this issue? As per some github answer I have found this can be solved using useEffect. SOURCE
Can anyone help me with this?
ISSUE #2
In this way, I have to wrap each and every protected component with my HOD. Is this the correct way of doing this or is there any other way to do this better than this?
Thank you so much or your support.
After spending some time. I was able to fix the issue by using below code. Now I just want to know the answer for 2nd issue mentioned above.
ISSUE #2 In this way, I have to wrap each and every protected component with my HOD. Is this the correct way of doing this or is there any other way to do this better than this?
Thank you so much or your support.
Code I use to fix the issue
import Router from 'next/router'
import Cookies from 'js-cookie';
import React, { useState, useEffect } from 'react';
const withAuth = (WrappedComponent) => {
return (props) => {
const [isLoggedIn, setLoginStatus] = useState(false);
useEffect(() => {
if (typeof window !== "undefined") {
const accessToken = Cookies.get('token');
if (accessToken) {
setLoginStatus(true)
}
else {
Router.push("/")
}
}
}, []);
if (isLoggedIn) {
return <WrappedComponent {...props} />;
} else {
return null;
}
}
};
export default withAuth;
When changing my id (/movie/:id), i'm re rendering my whole component. Sometimes i have to click 3 or 4 times on my like to have a change and sometimes i have only to click once(but im one component behind).
Here is my code :
import React from "react";
import "../styles/DetailFilm.css"
import {Link} from 'react-router-dom';
const API_IMAGES = 'https://image.tmdb.org/t/p/w500';
class DetailFilm extends React.Component {
constructor(props) {
super(props);
this.state = {
id: props.movie_id,
info: {},
recommandation: []
}
}
componentDidMount() {
const fetchData = async () => {
//fetch api
this.setState({info: data,recommandation:data_recommandation_spliced })
}
fetchData();
}
componentWillReceiveProps(nextProps) {
console.log("RENDERING" + nextProps.movie_id)
const fetchData = async () => {
// fetch api
this.setState({id: nextProps.movie_id,info: data,recommandation:data_recommandation_spliced })
console.log("Rendered" + nextProps.movie_id)
}
fetchData();
}
render() {
return (
//css
{this.state.recommandation.map((movie) =>
<Link to={`/movie/${movie.id}`}>
<img src = {API_IMAGES + movie.poster_path} className="image-movie-genre"/>
</Link>
)}
)
}
}
export default DetailFilm;
Thanks for helping !
When adding JSX elements from an array, each one needs a unique key property so that React can keep track of necessary changes in the DOM. You need to add keys to your Link elements so that React will know to update them.
I found a solution which wasn't the one i was looking for at first.
I changed from using a Class to a function using useEffect avec id as param.
I am trying to write a thing that lets the user move through posts. So you look at a particular post, and then you can go to the previous or next post. I am trying to do this with react router. So say the user looks at posts/3, then by clicking NEXT he or she will be redirected to posts/4 and then see post no. 4.
However, it does not work yet. Clicking the buttons works fine, and it also does change the URL in the browser. However, I do not know how I can then fetch a new post (and populate my currentPost reducer anew), whenever the route changes.
What I have so far is this:
import React from 'react'
import {connect} from 'react-redux'
import {fetchPost} from '../actions/currentPost.js'
class PostView extends React.Component {
constructor(props) {
super(props);
this.setNextPost = this.setNextPost.bind(this);
this.setPreviousPost = this.setPreviousPost.bind(this);
}
componentDidMount() {
const {id} = this.props.match.params;
this.props.fetchPost(id);
console.log("HELLO");
}
setPreviousPost() {
var {id} = this.props.match.params;
id--;
this.props.history.push('/Posts/1');
}
setNextPost() {
var {id} = this.props.match.params;
id++;
this.props.history.push('/Posts/'+id);
}
render() {
return (
<div>
<h1>Here is a Post</h1>
<button onClick={this.setPreviousPost}>Previous</button>
<button onClick={this.setNextPost}>Next</button>
</div>
);
}
}
function mapStateToProps (state) {
return {
currentPost: state.currentPost
};
}
export default connect(mapStateToProps, {fetchPost})(PostView);
The lifecycle method you're looking for is componentWillReceiveProps
Here's more or less what it would look like:
class Component extends React.Component {
componentWillReceiveProps(nextProps) {
const currentId = this.props.id
const nextId = nextProps.id
if (currentId !== nextId) {
this.props.fetchPost(nextId)
}
}
}
from there, I think Redux/React will handle the rest for you.
I'm totally stuck with passing data from container to component When using Meteor and React. I thought I had just copied the code from Meteor React tutorial and customized a little bit, but it doesn't work somehow. What I wanna do is getting data from a database(QuestionModel) and simply output in a view. According to an error message in browser, this.props.questions in renderQuestions() is undefined... But I explicitly passed the data to the component.
Could anyone help me out? Thank you.
import React, { Component, PropTypes } from 'react';
import { createContainer } from 'meteor/react-meteor-data';
import { Questions as QuestionsModel } from '../../api/questions.js';
import { QuestionList } from '../QuestionList.jsx';
class Questions extends Component{
renderQuestions(){
let filteredQuestions = this.props.questions;
//This doesn't work at all...
console.log(this.props.questions);
return filteredQuestions.map((question) => (
<QuestionList key={question._id} question={question} />
));
}
render(){
return (
<div className="questionlist">
<ul>{this.renderQuestions()}</ul>
</div>
);
}
}
Questions.propTypes = {
questions: PropTypes.array.isRequired,
};
export default QuestionsContainer = createContainer(() => {
//This console.log outputs the data correctly.
console.log(QuestionsModel.find({}).fetch());
const questions = QuestionsModel.find({}).fetch();
return {
questions,
};
}, Questions);
What is the code of your QuestionList component?
I just replaced it with <li key={question._id}>{question.question}</li> and it works perfectly for me.
Something else: do you import /imports/api/questions.js'; server-side?
Here is my code:
import React, { Component, PropTypes } from 'react';
import { createContainer } from 'meteor/react-meteor-data';
import { Questions as QuestionsModel } from '../api/questions.js';
// import { QuestionList } from '../QuestionList.jsx';
class Questions extends Component{
renderQuestions(){
let filteredQuestions = this.props.questions;
//This doesn't work at all...
console.log(this.props.questions);
return filteredQuestions.map((question) => (
<li key={question._id}>{question.question}</li>
));
}
render(){
return (
<div className="questionlist">
<ul>{this.renderQuestions()}</ul>
</div>
);
}
}
Questions.propTypes = {
questions: PropTypes.array.isRequired,
};
export default QuestionsContainer = createContainer(() => {
//This console.log outputs the data correctly.
console.log(QuestionsModel.find().fetch());
const questions = QuestionsModel.find({}).fetch();
return {
questions,
};
}, Questions);
I've gone through many of the Redux and ReactJS tuts. I understand setting actions => action creators => dispatch => store => render view (uni-directional flow) with more data substantial events. My problem is dealing with very simple events that change state. I know not all state always needs to be handled in Redux, and that local state events (set on React components) is an acceptable practice. However, technically Redux can handle all state events and this is what I am trying to do.
Here is the issue. I have a React component that renders a Button. This Button has an onClick event that fires a handleClick function. I set the state of the Button via the constructor method to isActive: false. When handleClick fires, setState sets isActive: true. The handleClick method also runs two if statements that, when either evaluate to true, run a function that either changes the background color of the body or the color of paragraph text. Clicking the same button again sets state back to false and will change back the body color or text color to the original value. This Button component is created twice within a separate component, Header. So long story short, I've got two buttons. One changes body color, the other changes p tag color after a click event.
Here's the code for the Button component:
import React, {Component} from 'react';
import {dimLights, invertColor} from '../../../actions/headerButtons';
import { connect } from 'react-redux';
import { Actions } from '../../../reducers/reducer';
const headerButtonWrapper = 'headerButton';
const headerButtonContext = 'hb--ctrls ';
const dimmedLight = '#333333';
const invertedTextColor = '#FFFFFF';
export default class Button extends Component {
constructor (props) {
super(props)
this.state = {
isActive: false
};
}
handleClick (e) {
e.preventDefault();
let active = !this.state.isActive;
this.setState({ isActive: active });
if(this.props.label === "Dim The Lights"){
dimLights('body', dimmedLight);
}
if(this.props.label === "Invert Text Color"){
invertColor('p', invertedTextColor)
}
}
render() {
let hbClasses = headerButtonContext + this.state.isActive;
return (
<div className={headerButtonWrapper}>
<button className={hbClasses} onClick={this.handleClick.bind(this)}>{this.props.label}</button>
</div>
);
}
}
Here's the code for the imported functions that handle changing the colors:
export function dimLights(elem, color) {
let property = document.querySelector(elem);
if (property.className !== 'lightsOn') {
property.style.backgroundColor = color;
property.className = 'lightsOn'
}
else {
property.style.backgroundColor = '#FFFFFF';
property.className = 'lightsOff';
}
}
export function invertColor(elem, textColor) {
let property = document.querySelectorAll(elem), i;
for (i = 0; i < property.length; ++i) {
if (property[i].className !== 'inverted') {
property[i].style.color = textColor;
property[i].className = 'inverted'
} else {
property[i].style.color = '#3B3B3B';
property[i].className = 'notInverted';
}
}
}
Here's the code for the reducers:
import * as types from '../constants/ActionTypes';
const initialState = {
isActive: false
};
export default function Actions(state = initialState, action) {
switch (action.type) {
case types.TOGGLE_LIGHTS:
return [
...state,
{
isActive: true
}
]
default:
return state
}
}
Here's the code for the actions:
import EasyActions from 'redux-easy-actions';
export default EasyActions({
TOGGLE_LIGHTS(type, isActive){
return {type, isActive}
}
})
If it helps, here's the Header component that renders two Button components:
import React, {Component} from 'react';
import Button from './components/Button';
const dimmer = 'titleBar--button__dimmer';
const invert = 'titleBar--button__invert';
export default class Header extends Component {
render() {
return (
<div id="titleBar">
<div className="titleBar--contents">
<div className="titleBar--title">Organizer</div>
<Button className={dimmer} label="Dim The Lights" />
<Button className={invert} label="Invert Text Color" />
</div>
</div>
);
}
}
Finally, here's the code containing the store and connection to Redux (NOTE: Layout contains three main components Header, Hero, and Info. The Buttons are created only within the Header component)
import React, { Component } from 'react';
import { combineReducers } from 'redux';
import { createStore } from 'redux'
import { Provider } from 'react-redux';
import Layout from '../components/Layout';
import * as reducers from '../reducers/reducer';
const reducer = combineReducers(reducers);
const store = createStore(reducer);
// This is dispatch was just a test to try and figure this problem out
store.dispatch({
type: 'TOGGLE_LIGHTS',
isActive: true
})
console.log(store.getState())
export default class Organizer extends Component {
render() {
return (
<Provider store={store}>
<div>
<Layout />
</div>
</Provider>
);
}
}
What I am looking to do is remove the state logic from the local React component and into Redux. I feel like the functions I have imported need to act as dispatchers. I also feel like I am setting up my initial actions incorrectly. This is such an incredibly simple event that finding an answer anywhere online is difficult. Anyone have any thoughts on what I can do to fix this?
You're almost there. It looks like you've left out the code for Layout component, which I assume is the component that's rendering your Button. The critical piece here is going to be your container, which is the component that's wrapped with Redux's connect to link it to the store. Docs for this. More details here.
What you did:
// components/Button.js - pseudocode
import {dimLights, invertColor} from '../../../actions/headerButtons';
handleClick() {
dimLights();
}
What Redux wants you to do instead:
// containers/App.js - pseudocode
import {dimLights, invertColor} from '../../../actions/headerButtons';
class App extends Component {
render() {
// Pass in your button state from the store, as well as
// your connected/dispatch-ified actions.
return (
<Button
state={this.props.buttonState}
onClick={this.props.buttonState ? dimLights : invertColor}
/>
);
}
}
function mapStateToProps(state, ownProps) {
return {
buttonState: state.buttonState
};
}
export default connect(mapStateToProps, {
// Your action functions passed in here get "dispatch-ified"
// and will dispatch Redux actions instead of returning
// { type, payload }-style objects.
dimLights, invertColor
})(App);
Hope that helps! Redux has a lot of boilerplate for simple stuff like this, however, because most of the pieces can be expressed as pure functions, you gain a lot in unit testing flexibility, and get to use the devtools debugger.