Unable to access function from parent - sending props from parent to child - javascript

I have an app which has a navbar, and a display below that shows all the posts.
Here is how it is setup:
var routes = (
<Router history={createHistory()}>
<Route path="/" component={App}>
<Route path="/submit" component={CreatePost}/>
</Route>
<Route path="*" component={NotExist}/>
</Router>
)
So, if you click on "New" in the navbar, you will be taken to server/submit and a form will appear where you can enter details for a new post. On submit, it will add the details to the posts state object.
Inside of App, I have a function called addToPosts().
render : function() {
//<CreatePost addPostToPosts={this.addPostToPosts} posts={this.state.posts}/>
return (
<div>
<NavigationBar/>
{this.props.children}
<DisplayPosts postData={this.state.posts} />
</div>
)
}
#kirill-fuchs yesterday told me to use {this.props.children} to send the properties. However, when I check react console, I see that props are empty. But, I know it is doing something because if I get rid of the {this.props.children} then clicking on New doesn't do anything. But when I put it back, it redirects to the form.
In addition, when I click submit in the form, it says this.props.addToPosts is not a function, because the props are empty.
Can someone please help me?

It sounds like you want to pass props down to the children. You can use the following to do that:
{React.cloneElement(this.props.children, {addToPosts: this.addPostToPosts, posts: this.state.posts})}
This will pass a prop into the CreatePost component that you can then call when the submit button is pressed.

Related

How to implement data update from one component which might affect other components

I am implementing a todo application which has several pages where each page displays a filtered list of all todos depending on the todos' references.
In addition, there is a global "Add task" button and modal from which users can add a task with references. Depending on which page the user is on when adding a task, this newly added task must either be shown right away because the reference is included in currently active page's filter or it must not be shown.
For example, if the user is on the page "Project 1"
If user adds task for Project 1 -> show on page
If user adds task for Project 2 -> don't show on page
Currently, App.js looks something like this
function App() {
return (
<Page>
<Router>
<Route path="/project1" element={<TaskListProject1 />} />
<Route path="/project2" element={<TaskListProject2 />} />
<Router />
<AddTaskButtonAndModal />
<Page />
);
}
How do I best set up and manage the state so that the components TaskListProject1 and TaskListProject2 are only updated if the added task must be included there?
I was thinking about adding the task from the AddTaskButtonAndModal component to the data base and then let the currently active component retrieve all their tasks from the data base again. For this, I would introduce a dummy state whose sole purpose is triggering a rerender of components like so
function App() {
const [dummyState, setDummyState] = useState(0);
return (
<Page>
<Router>
<Route path="/project1" element={<TaskListProject1 dummyState={dummyState} />} />
<Route path="/project2" element={<TaskListProject2 dummyState={dummyState} />} />
<Router />
<AddTaskButtonAndModal setDummyState={setDummyState} />
<Page />
);
}
...but that feels like I'm misusing states.
Another idea was to always have a complete list of all tasks in the top level state and let each component filter for their tasks when it is rendered. But then I need to always have all tasks in the state when only a fraction of tasks are actually needed.
As you can probably imagine, I'm new to React, so I might simply miss a basic concept. Thanks for any help!

Navbar with dynamic content and behaviour

Say I have the following App.js:
App.js
render() {
return (
<div>
<Navbar />
<Router>
<Home path="/" />
<RouteA path="/routeA" />
<RouteB path="/routeB" />
</Router>
</div>
);
}
So I need the <Navbar /> to behave and look differently in the /routeA and /routeB.
For example, there is a back button in the <Navbar />. And when user is in /routeA, clicking the back btn will go back one step in history.
However, when user is in /routeB, not only that clicking back btn will now pops up an alert, we also need to render a component that has updating props inside the <Navbar />.
So the way I go about this is, well, redux: The <Navbar /> is connected to its own slice of state in the store. Then, in my routes, in order to make the navbar dynamic accordingly, I dispatch actions with my function and component to make the <Navbar /> behave and look the way I want. So far so good.
Problem is we can't serialize 'function' and 'component' objects in the store and subsequently localstorage...so like on refresh, they are gone...
So my workaround for the 'function' is I immediately dispatch it in the routes' constructor. Whereas for the 'component', I use Portal in my routes' render method to insert it to my <Navbar />...
My question is how would you implement this differently?
Thank you for reading!
You can send function to route components and call this funciton when component is mounted, you can have functions defined inside NavBar component, and switch based on the page you are on.
updateLayout = (page) => {this.setState({currentPage: page})}
render() {
return (
<div>
<Navbar currentPage={this.state.currentPage} />
<Router>
<Home path="/" render={()=> <ComponentHome updateLayout={this.updateLayout}>} />
<RouteA path="/routeA" render={()=> <ComponentA updateLayout={this.updateLayout}>} />
<RouteB path="/routeB" render={()=> <ComponentB updateLayout={this.updateLayout}>} />
</Router>
</div>
);
}

How to get local state of another component using Redux

Using react + redux, I am making an app where you answer questions, 5 questions per step and about 20 steps. When starting a new step, I save the progress to localStorage.
My app structure is like this:
class App extends Component {
render() {
return (
<Provider store={store}>
<Router>
<div id="ocean-model">
<Topbar />
<main id="main">
<Route exact path="/" component={Landing} />
<Switch>
<PrivateRoute exact path="/survey" component={Survey} />
</Switch>
<Switch>
<AdminRoute exact path="/edit-questions" component={EditQuestions} />
<AdminRoute exact path="/add-question" component={AddQuestion} />
</Switch>
</main>
</div>
</Router>
</Provider>
);
}
}
In Survey component I use controlled inputs to save current answers to local state and on submitting all 5 answers I use redux action to save given answers to localStorage and redux store.
In Topbar component I have a button, which onClick I would like to take the current progress from redux store, but also get the currently answered questions(for example 2 out of 5), which are only available in the Survey component's local state.
Do I need to modify the App structure so that Topbar and Survey can share state or maybe in Survey I need to somehow listen to an onclick event of a button from Topbar?
Is there any reason you can't save given answer to localStorage and redux store when the input loses focus? Might need to allow for the action to fire if the Topbar component button is clicked while the focus is still on the last input with an answer.
The alternative would be to use React Context as your state container for each set of 5 questions and use that from both the form and the Topbar button with a Provider located in the component tree above both.

Multiple ReactDOM.render calls with shared router context

I got two React components rendered like this.
ReactDOM.render(
<Router history={ browserHistory }>
<Route path='/items' component={ App }>
<IndexRoute component={ Home } />
<Route path='/items/:id' component={ Details } />
</Route>
</Router>,
document.getElementById('app')
);
And the second one, which is a sidebar.
ReactDOM.render(
<Sidebar />,
document.getElementById('sidebar')
);
I'd like to use the Link helper from react-router in the Sidebar component. However, I get the following error: "Uncaught Invariant Violation: Links rendered outside of a router context cannot navigate.". Which makes sense, because the sidebar is not within the router context like the first seen above.
Is there a way to share the router context with the sidebar?
I'd like to change the sidebar layout based on the route (and access the router object in this.props properly) and use Link as it should be.
I don't want to work my way around hacky approaches like history.pushState, or parsing location.path to change the sidebar's layout according to the corresponding route of items.
You should be able to just render your sidebar in its own <Router>. The important thing is that they share the same history instance (in this case, browserHistory).
When a <Router> mounts, it adds a listener to its history instance. When a history instance receives a new location, it notifies all of its listeners.
ReactDOM.render((
<Router history={browserHistory}>
<Route component={Sidebar} />
</Router>
) document.getElementById('sidebar')

Basic way to handle login with React

I'm working on a client-side app using create-react-app. The application renders a login component with a basic form and I want to load another component (which will be the main application) on successful login.
The validation and the login logic is not the issue at the moment because first I'm trying to figure out a simple way to dismount the login component and load another component on the submit event.
class Login extends Component {
handleLogin(){
// trigger to load Main.js
}
render() {
return (
// form elements here
<div className="submit">
<input className="button-signin" value="Sign In" type="submit"
onClick={this.handleLogin}/>
</div>
);
}
}
What would be the easiest way to switch to another component (which I called Main.js in this example) on submit event?
Have you tried using React router? It makes really easy to define routes and assign a Component to each of them. Then you can also establish conditions for accessing each route. There is an example in the official docs that seems to match your requirements.
<Router history={withExampleBasename(browserHistory, __dirname)}>
<Route path="/" component={App}>
<Route path="login" component={Login} />
<Route path="logout" component={Logout} />
<Route path="about" component={About} />
<Route path="dashboard" component={Dashboard} onEnter={requireAuth} />
</Route>
The method requireAuth will be checked each time you try to access the route dashboard (which will show the component Dashboard). If you are using token-based authentication, you might have a token stored in the localStorage and check if it's present in requireAuth.
Your method handleLogin would call the backend, log the user in (store the token in localStorage if that's how the login is handled) and then redirect to the dashboard route.

Categories

Resources