I am practising React props by building an input form using hooks. The idea is when I enter the inputs in NewList component and click the submit button, its values will render on the MyJobList component in a form of HTML elements. The issue is when I submitted the form, nothing is displaying on the web page. It could be that I am passing the data incorrectly.
Here are my codes. I have minimized it to highlight possible problems and included a link to full codes down below:
newlist.js
const NewList = () => {
const [inputState, setInputState] = useState({});
const onSubmitList = () => {
setInputState({
positionTitle: `${inputs.positionTitle}`,
companyName: `${inputs.companyName}`,
jobLink: `${inputs.jobLink}`
});
};
const { inputs, handleInputChange, handleSubmit } = CustomForm(onSubmitList);
myjoblist.js
const MyJobList = inputs => (
<div>
<h3>{inputs.positionTitle}</h3>
<p>{inputs.companyName}</p>
<p>{inputs.jobLink}</p>
</div>
);
navbar.js
const Navbar = inputState => (
<Router>
<Switch>
<Route path="/my-list">
<MyJobList inputs={inputState} />
</Route>
</Switch>
</Router>
);
Any help and guidance are much appreciated.
Here's a link to complete code: Code Sandbox
So, you need the persisting state to live in a shared parent component, in the existing components, your choices would be either App.js or navbar.js (side note, you should PascalCase your component file names, so NavBar.js). Ideally, you should make a shared container that will hold the state. When you navigate away from the newlist component to view myjoblist, the newlist component unmounts and you lose the state. With a shared parent component, the parent won't unmount when it renders its children (newlist & myjoblist).
Another problem is that you you are passing a callback to your custom hook, but the callback doesn't have any arguments. In your handle click, you need to pass it the inputs. You also cannot set your state to an empty string before you pass the inputs to your callback, do it after.
const handleSubmit = event => {
e.preventDefault()
callback(inputs)
setInputs({}) // set it back to the default state, not a string
}
Lastly, the inputState in navbar is an undefined variable. You are rendering Navbar inside your app and not passing it any props. The first argument to a functional component in react is props. So, even if you were passing it state, you'd need to extract via props.inputState or with destructuring.
Here's a working example that could still use some clean up:
https://codesandbox.io/s/inputform-with-hooks-wwx2i
Related
I know that you can do navigation.navigate("address", {/* params go here */ to send parameters over to another screen. But then you have to navigate there. Is there a way of sending params over without navigating?
I have a application with multiple screens. And I want to update a useState from another component by updating its params so that a button appears. But I dont want to navigate there, I just want to update it so when the user does go there the button will be there.
Like this:
const currentComponent = (navigation) {
return (
<Button onPress={navigation.updateParams("otherComponent", {shouldShowValue: true})} />
)
}
const otherComponent = (route, navigation) {
const {shouldShowValue} = route.params
const [shouldShow, setShouldShow] = useState(shouldShowValue);
return (
{shouldShow ? <Button> Yayy this button appears now <Button /> : null}
)
}
}
'''
this is just pseudo code and not at all
like the code I have written,
but its just meant as an example to get a
understanding of what I mean.
(updateParams) isnt a function that exists,
but I want something similiar like it.
Is there a way of updating the params in a
component from another component without having
to navigate there? Like with
navigate.navigate("address" {params go here})
but without the navigation part?
You can consider using useContext() hook to execute your functionality.
Using navigation library to pass param without navigating to that page is somehow misusing the navigation function.
With useContext, you can share the state(s) among components. If you want to change the value upon clicking action, you can also pass the useState hook into useContext. Alternatively, you can consider to use redux library to share state.
import { useState, createContext, useContext } from 'react';
const shareContext = createContext(null);
export default function demoUseContext() {
const [isClicked, setClicked] = useState(false);
return (
<shareContext.Provider value={{isClicked, setClicked}}>
<ComponentA />
<ComponentB />
</shareContext.Provider>
)
}
function ComponentA() {
const sharedParam = useContext(shareContext);
return (
<button onClick={() => sharedParam.setClicked(!sharedParam.isClicked)}>
click to change value
</button>
);
}
function ComponentB() {
const sharedParam = useContext(shareContext);
return (
sharedParam.isClicked && <div>it is clicked</div>
)
}
As the example above, the code pass the useState hook from parent component into context, where A is consuming the useState from context to setup isClicked via setClicked, B is consuming the value isClicked from context.
You can also manage to setup context with value not only in a hook, but a param / object / function as a callback.
For more details, please refer to https://reactjs.org/docs/hooks-reference.html#usecontext
There're multiple hooks including useContext fyi
Passing parameters to routes
There are two pieces to this:
Pass params to a route by putting them in an object as a second parameter to the navigation.navigate function: navigation.navigate('RouteName', { /* params go here */ })
Read the params in your screen component: route.params.
We recommend that the params you pass are JSON-serializable. That way, you'll be able to use state persistence and your screen components will have the right contract for implementing deep linking.
I'm relatively new to React and JavaScript and I'm building a website but I'm having a bit of an issue with passing Data via components from child to parent.
So:
I have my App.js script which is used as a router with react-router-dom, but I would like to store a boolean value using the useState hook. This boolean value that I would like stored should be passed on from a component called Login. I have the script setup to pass that data however the boolean is only stored as long as the Login COmponent page is active and when it is not rendered the boolean store by the useState hook in the App.js script just goes to 'undefined'. I'm assuming that this is happening because the app.js page constantly re-loads and re-renders, so how could I store that value even when the login page is not being rendered?
This is the code setup to pass that data:
app.js
const [authValue, setAuthValue] = useState(false);
const changeValue = (value) => {
setAuthValue(value)
}
And where the Login is called:
<Route path='/signin' element={<Login changeValue={changeValue}value={authValue} />} />
Login.jsx:
const Login = ({changeValue, value}) => {
const [isValid, setIsValid] = useState(true)
changeValue(isValid)
}
The useState hook in React is a solution for component-level state management in functional components.
So, the value of isValid is stored only in Login.js and can be passed as a props to its children components.
Of course, you can also pass some state (or values) from child to parent via functions passed from parent into the child, but this is not the way you should consider if you want to use a state in whole app.
If you need to have the state that should be persistent across the app components, even after one component was unmounted, you should consider global management solutions like React Context API, Redux, MobX or similar libraries.
Try putting the changeValue(isValid) into a useEffect() hook. So:
useEffect(() => {
changeValue(isValid)
}
Background
So I have a simple example, a Form component (Parent) and multiple Input components (Children). Each individual Input component will have an useState hook to initialize and manage its value's state.
Issue
As with most forms, I would like to submit all of the data to a backend for processing. However, the issue is that I cannot retrieve the state of value from each Child Input component.
// app.jsx
import Form from "./Form";
export default function App() {
return <Form />;
}
// Form.jsx
import React from "react";
import Input from "./Input";
const handleSubmit = (e) => {
e.preventDefault();
console.log("Wait, how do I retreive values from Children Inputs?");
};
const Form = () => {
console.log("Form render");
return (
<form onSubmit={handleSubmit}>
Sample Form
<Input initial="username" name="user" />
<Input initial="email" name="email" />
<button type="submit">Submit</button>
</form>
);
};
export default Form;
// Input.jsx
import React from "react";
import useInputValue from "./useInputValue";
const Input = ({ name, initial }) => {
const inputState = useInputValue(initial);
console.log(`${name}'s value: ${inputState.value}`);
return <input {...inputState} />;
};
export default Input;
Plausible Solution
Of course, I can lift the Input states up to the Form component, perhaps in an obj name values. However, if I do that, every time I change the Inputs, the Form will re-render, along with all of the Inputs.
To me, that is an undesirable side-effect. As my Form component gets bigger, this will be more costly to re-render all inputs (inside the form) every time one input changes.
Because of that, I would like to stick with my decision of having each individual input manage its own state, that way if one input changes, not all other input will re-render along with the Parent component.
Question
If each of the Child components manages its own state, could the Parent component access the value of that state and do actions (like form submission)?
Update
Many answers and comments mentioned that this is premature optimization and the root of all known evil; which I agree with, but to clarify, I am asking this question with a simple example because I wanted to find a viable solution to my current and more complex project. My web app has a huge form (where all states are lifted to the form level), which is getting re-rendered at every change in its inputs. Which seemed unnecessary, since only one input is changed at a time.
Update #2
Here is a codesandbox example of the issue I am having. There are two forms, one with all states managed by individual Child input components, and the other has all states lifted up in the form level. Try entering some values and check the console for log messages. One can see that with all states lifted to the form level, every change will cause both inputs to be re-rendered.
I think yes, you can share state. Also there are 3 options:
I recommend you to use such library as Formik. It will help you in your case.
You can share state using useState() hook as props.
Use such tools as Redux Toolkit (if we are speaking about memoisation), useContext() and etc.
If the thing you want is getting final values from input, assign ref to each input and access using emailRef.current.value in the submit function.
import { useState, useRef, forwardRef } from 'React';
const Input = forwardRef((props, ref) => {
const [value, setValue] = useState('');
return <input ref={ref} value={value} onChange={(e) => {setValue(e.target.value)}} {...props} />;
});
const App = () => {
const emailRef = useRef(null);
const submit = () => {
const emailString = emailRef.current.value
};
return (
<>
<Input ref={emailRef} />
<button onClick={submit}>Submit</button>
</>
);
};
If the parent needs to know about the childs state, you can
move the state up. This is resolved by passing down a setState function that the child calls (the states data is available in the parent)
use a context https://reactjs.org/docs/context.html
use a state management library e.g. redux
In your simple case I'd go with 1)
I am trying to build an ecommerce website, and I hit a problem I cannot seem to resolve. I am very new to react and JS so have some patience please :)
I declared 4 useStates in my app.js:
const [elementeDinState, setElementeDinState] = useState([]);
const [currentCategorie, setCurrentCategorie] = useState("Acasa");
const [subCategorie, setSubcategorie] = useState([]);
const [cartContents, setCartContents] = useState([]);
const fetchData = useCallback(async () => {
const data = await getCategories();
setElementeDinState(data);
}, []);
useEffect(() => {
fetchData().catch(console.error);
}, [fetchData]);
const changeHeader = (dataFromMenuItem) => {
setCurrentCategorie(dataFromMenuItem);
};
const changeCopiiContent = (data1FromThere) => {
setSubcategorie(data1FromThere);
};
const changeCart = (dataFromCart) => {
setCartContents(dataFromCart);
};
I am passing the functions to change those states to different child components as props. my problem is, when I add items to cart it triggers a re render of my component (products listing component) that should not be affected by cartContents and that resets the state of said component to the initial value that changes the items being shown. does useState hook create a single global state comprised of all those states?
If these useState are defined in the app.js and then passed down, when a child will use them chasing the state will happen in the app.js so all the children of <App /> will be re-rendered.
I guess that your app.js looks similar:
function App() {
const [elementeDinState, setElementeDinState] = useState([]);
// ...and the other hooks and methods
return (
<cartContents setElementDinState={setElementeDinState} />
<ProductList />
)
}
In this case the state is in the component so when <CartContents /> changes it, it will trigger a re-render of the and all its children <ProductList /> included.
To avoid this problem think better when each piece of state needs to be and put the state as near as possibile to that component. For example, if the state of the cart does not influence the Product list. Move the useState in the <Cart /> component.
From what I understand, your problem is that you're simply resetting the cartContents state every time you call the changeCart function, correct?
What you probably want, is to add (or remove ?) the item to the cart, like this?
const changeCart = (dataFromCart) => {
setCartContents(oldContents => [...oldContents, dataFromCart]);
};
Here is a description of useState from the oficial site:
useState is a Hook (...). We call it inside a function component to add some local state to it
So it creates just a local state.
About your problem, We need more information, but I believe that some parent component of that widget is trying to render other component instead of your the component that you wanted (let's call it "ProblemComponent") and rendering you ProblemComponent from scratch again, before you can see it.
it's something like that:
function ParentComponent(props: any) {
const isLoading = useState(false);
// Some logic...
if(isLoading) {
return <LoadingComponent/>;
}
return <ProblemComponent/>;
}
If that doesn't work you can also try to use React.memo() to prevent the ProblemComponent to update when it props change.
well, seems like I wanted to change the way react works so I figured out a work around, based on what you guys told me. I declared the state of the productsComponent in the parent component and adding to cart now doesn't force a refresh of the items being shown. thank you!
I have a route which takes an id and renders the same component for every id, for example :
<Route path='/:code' component={Card}/>
Now the in the Link tag I pass in an id to the component.Now the Card component fetches additional detail based on the id passed. But the problem is it renders only for one id and is not updating if I click back and goto the next id. I searched and found out that componentsWillReceiveProps can be used but during recent versions of React it has been deprecated. So how to do this?
Putting current location as key on component solves problem.
<Route path='/:code' component={(props) => <Card {...props} key={window.location.pathname}/>}/>
I just ran into a similar problem. I think you are conflating updating/rerendering and remounting. This diagram on the react lifecycle methods helped me when I was dealing with it.
If your problem is like mine you have a component like
class Card extend Component {
componentDidMount() {
// call fetch function which probably updates your redux store
}
render () {
return // JSX or child component with {...this.props} used,
// some of which are taken from the store through mapStateToProps
}
}
The first time you hit a url that mounts this component everything works right and then, when you visit another route that uses the same component, nothing changes. That's because the component isn't being remounted, it's just being updated because some props changed, at least this.props.match.params is changing.
But componentDidMount() is not called when the component updates (see link above). So you will not fetch the new data and update your redux store. You should add a componentDidUpdate() function. That way you can call your fetching functions again when the props change, not just when the component is originally mounted.
componentDidUpdate(prevProps) {
if (this.match.params.id !== prevProps.match.params.id) {
// call the fetch function again
}
}
Check the react documentation out for more details.
I actually figured out another way to do this.
We'll start with your example code: <Route path='/:code' component={Card}/>
What you want to do is have <Card> be a wrapper component, functional preferrably (it won't actually need any state I don't think) and render the component that you want to have rendered by passing down your props with {...props}, so that it gets the Router properties, but importantly give it a key prop that will force it to re-render from scratch
So for example, I have something that looks like this:
<Route exact={false} path="/:customerid/:courierid/:serviceid" component={Prices} />
And I wanted my component to rerender when the URL changes, but ONLY when customerid or serviceid change. So I made Prices into a functional component like this:
function Prices (props) {
const matchParams = props.match.params;
const k = `${matchParams.customerid}-${matchParams.serviceid}`;
console.log('render key (functional):');
console.log(k);
return (
<RealPrices {...props} key={k} />
)
}
Notice that my key only takes customerid and serviceid into account - it will rerender when those two change, but it won't re-render when courierid changes (just add that into the key if you want it to). And my RealPrices component gets the benefit of still having all the route props passed down, like history, location, match etc.
If you are looking for a solution using hooks.
If you are fetching data from some API then you can wrap that call inside a useEffect block and pass history.location.pathname as a parameter to useEffect.
Code:
import { useHistory } from "react-router";
const App = () => {
const history = useHistory();
useEffect(() => {
//your api call here
}, [history.location.pathname]);
};
useHistory hook from react-router will give the path name so the useEffect will be called everytime it (url) is changed
as described by #theshubhagrwl but
you can use location.href instead of location.pathname to work in all condition
import { useHistory } from "react-router";
const App = () => {
const history = useHistory();
useEffect(() => {
// do you task here
}, [history.location.href]);
};
You can use use UseLocation() from "react-router-dom"
and then use that object in useEffect dependency array.
import {useLocation} from "react-router-dom";
export default function Card() {
const location = useLocation();
useEffect(()=>{}, [location]);
return(
// your code here
);
}
In React Router v4 Adding a Switch tag after Router fixes the problem